Программирование на XML для DB2: Часть 2. Использование встроенной в базу данных поддержки XML в вашей архитектуре приложений

Вы узнаете, как новая среда хранения XML и запросов базы данных IBM® DB2® 9 for Linux®, UNIX® и Windows® сочетаются с моделью данных XML, которая была описана в первой части данной серии. В этой второй части серии мы поговорим о том, как использовать улучшенную поддержку XML, реализованную в базе данных, в архитектуре ваших приложений.

Хардип Сингх (Hardeep Singh), разработчик инструментария XML для DB2, IBM

Хардип Сингх (Hardeep Singh) – член группы встроенной поддержки XML в DB2 и архитектор инструментария для XML DB2 (DB2 XQuery Builder). Он также отвечает за стратегию переноса XML-приложений для следующей версии универсальной базы данных DB2. Его опыт работы в промышленности насчитывает 21 год. .



13.05.2009

Введение

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

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


Основы XML для баз данных

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

DB2 позволяет хранить, запрашивать, обслуживать и публиковать:

  • реляционные данные — SQL
  • реляционные данные в виде XML — SQL/XML
  • XML-данные — XQuery
  • смешанные данные (реляционные и XML) — SQL/XML и XQuery
Рисунок 1. Хранение смешанные данных в DB2
Хранение смешанные данных в DB2

Хранение XML в базе данных

Главным преимуществом поддержки XML в реляционной базе данных является то, что вы можете сохранять как реляционные, так и XML-данные в одной таблице. И хотя на самом деле XML хранится в иерархическом (древовидном) формате, логически в таблице базы данных он отображается в одном столбце (как CLOB или BLOB).

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

Листинг 1. Таблицы
CREATE TABLE CUSTOMER_TABLE (
CUSTOMERID CHARACTER (12) NOT NULL,
CUSTXML XML NOT NULL ,
CONSTRAINT CC1183665042494 PRIMARY KEY ( CUSTOMERID) )

CREATE TABLE PURCHASE_TABLE (
CUSTOMERID CHARACTER (12) NOT NULL ,
ITEMXML XML NOT NULL ,
CONSTRAINT CC1183665244645 FOREIGN KEY
(CUSTOMERID) REFERENCES CUSTOMER_TABLE (CUSTOMERID)
ON DELETE CASCADE ON UPDATE NO ACTION
ENFORCED ENABLE QUERY OPTIMIZATION )

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

Так как логически XML-столбец отображается так же, как столбец ARCHAR, CLOB или BLOB, функции INSERT для них также будут похожи.

insert into CUSTOMER_TABLE values('hardeep',
'<Customer customerid="hardeep" firstname="hardeep" lastname="singh"/>')

Или, если бы вы делали вставку из программы Java™:

Листинг 2. Вставка из Java-программы
String insertsql= "insert into PURCHASE_TABLE values(?,?)";
PreparedStatement iStmt=connection.prepareStatement(insertsql);
File inputfile= new File(filename); //filename is the path of the XML file
long filesize=inputfile.length();
BufferedReader in = new BufferedReader(new FileReader(inputfile));
iStmt.setCharacterStream(1,in,(int)filesize);
int rc= iStmt.executeUpdate();

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

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

Рисунок 2. Логическое представление хранения смешанных данных в DB2
Рисунок 2. Логическое представление хранения смешанных данных в DB2

Запрос XML

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

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

SELECT CUSTXML FROM CUSTOMER_TABLE where customerid='hardeep';

Этот запрос вернет данные из столбца CUSTXML для пользователя с именем hardeep.

Теперь рассмотрим случай, когда нам нужны данные пользователя, где фамилия (lastname) пользователя - сингх (singh). Тогда нам надо определить путь к атрибуту lastname в каждом XML-документе (CUSTOMER_TABLE.CUSTXML/Customer/@lastname) и проверить, есть ли там значение "singh".

В идеальном мире запрос был бы таким: Select * from CUSTOMER_TABLE where CUSTXML/Customer/@lastname='singh'. Однако в реальном мире нам надо сформулировать его так, чтобы он был понятен процессору запросов базы данных. В мир баз данных был введен новый язык, XQuery, который можно использовать для запроса XML-документов. В SQL были добавлены новые функции, понимающие этот новый язык и соединяющие эти два мира. В результате запрос на поиск пользователя с фамилией singh будет выглядеть так:

select CUSTXML from CUSTOMER_TABLE
where xmlexists ('$cust/Customer[@lastname= "singh" ]' passing CUSTXML AS "cust" )

Или, если вы делаете вызов из программы Java, применяя параметризованный запрос:

select CUSTXML from CUSTOMER_TABLE
where xmlexists ('$cust/Customer[@lastname= $lname ]'
passing CUSTXML AS "cust" , cast(? as VARCHAR(12)) as "lname")

Когда вы разберетесь со странным синтаксисом передачи параметров функциям SQL/XML, вы обнаружите, что для несложных смешанных запросов реляционных и XML-данных XML-запросы в основном содержат выражения XPath. Это очень похоже на то, что мы делали для уровня приложения (в первой части) для модели данных XML, где большую часть кода составляли вызовы XPath к оболочке объектной модели документов (DOM), предназначенные для запросов и управления XML-данными.

Замечание: В Viper 2 была несколько упрощена передача параметров некоторым функциям SQL/XML. Например, в предыдущем запросе в операторе передачи XMLExists не указывается столбец CUSTXML.

select CUSTXML from CUSTOMER_TABLE
where xmlexists ('$CUSTXML/Customer[@lastname= $lname ]'
passing cast(? as VARCHAR(12)) as "lname")

Перенос логики приложения в базу данных

XQuery имеет все элементарные функции большинства высокоуровневых языков (if-then-else, for, переменные, функции и арифметические операторы). Это дает возможность встраивать бизнес-логику в запрос. Кроме того, этот язык имеет функциональность, соответствующую преобразованиям XSLT, поэтому XQuery позволяет не только запрашивать XML, но и преобразовывать XML-результат прямо в базе данных.

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

<Customer customerid ="" firstname="" lastname="" >
<Items><Item ID="" description="" purchaseDate="" price="" /></Items>
</Customer>

Замена кода приложения запросом в DB2

Вместо того чтобы объединять XML-данные на уровне приложения, вы можете сделать то же самое в базе данных с помощью одного запроса SQL/XML. Используем соединение один-ко-многим для таблиц CUSTOMER_TABLE.CUSTXML/Customer/@customerid и PURCHASE_TABLE.ITEMXML/Customer/@customerid.

Рисунок 3. Объединение двух столбцов XML
Объединение двух столбцов XML
Листинг 3. Запрос к двум столбцам XML
values(xmlquery('


for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

where $Customer/@customerid= $customerid

return
<Customer customerid ="{$Customer/@customerid}"
firstname ="{$Customer/@firstname}" lastname ="{$Customer/@lastname}" >{

for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
where $Customer0/@customerid= $Customer/@customerid
return $Customer0/Item

}</Customer>

' passing cast( ? AS varchar(255) ) as "customerid" ))

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

Рисунок 4. Результат запроса
Результат запроса

В предыдущем запросе нам пришлось создавать внешний элемент Customer и добавлять атрибуты из данных столбца CUSTXML. В DB2 Viper 2 (бета) имеется поддержка обновляющих выражений XQuery, что позволяет изменять XML-документ и избавляет от необходимости создавать внешний элемент Customer. Вместо этого используйте соответствующий элемент из таблицы customer и добавляйте товары из таблицы purchase в качестве потомков.

Листинг 4. Запрос к двум столбцам XML в Viper 2
values(xmlquery('
				

for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

let $items:=(<Items>{

for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
where $Customer0/@customerid= $Customer/@customerid
return $Customer0/Item

}</Items>)

where $Customer/@customerid= $customerid

return
transform
copy $cust:=$Customer
modify(
do insert $items as last into $cust)
return $cust

' passing hardeep as "customerid" ))

В предыдущих запросах мы не только искали, извлекали и объединяли части XML-документов, хранящихся в базе данных, но и преобразовывали полученный XML, добавляя в него новые элементы. К тому же пользователь hardeep был неявно приведен к типу XML (xs:string).

Сравнение запросов базы данных и кода приложения на Java

Если вы сравните приведенные выше запросы с Java-кодом из первой части (Листинг 6. Переписывание приложения с использованием модели XML), то увидите, что логика у них очень похожа.

  1. Выбираем информацию о пользователе (Customer) из таблицы CUSTOMER_TABLE.
  2. Создаем элемент для товаров (Items) и ищем по всем товарам, купленным пользователем из таблицы PURCHASE_TABLE.
  3. Повторяем для каждого товара из выбранного списка и добавляем в элемент Items.
  4. Добавляем элемент Items к элементу Customer.

Создание хранимой процедуры

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

Листинг 5. Создание процедуры
CREATE PROCEDURE customerItems(IN custid varchar(12))
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c_cur CURSOR WITH RETURN FOR

values(xmlquery('

for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

let $items:=(<Items>{

for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
where $Customer0/@customerid= $Customer/@customerid
return $Customer0/Item

}</Items>)

where $Customer/@customerid= $customerid

return
transform
copy $cust:=$Customer
modify(
do insert $items as last into $cust)
return $cust

' passing custid as "customerid" ))
OPEN c_cur;
END

Замена кода приложения вызовом хранимой процедуры

Теперь код приложения вызывает хранимую процедуру в DB2 и передает XML в оболочку DOM. Код приложения для модели XML из первой части (Листинг 6. Переписывание приложения с использованием модели XML) уменьшится до нижеследующего:

2. ResultSet dbResult = dbstmt.executeQuery("call customerItems ("+custid+")"
3. XMLParse customerXML = new XMLParse(dbResult. getString(1));

Пример посложнее

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

<insurance>
<rate price="100" currency="$" rate=".02"/>
<rate price="500" currency="$" rate=".018"/>
<rate price="" currency="$" rate=".015"/>
</insurance>

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

Листинг 6. Хранимая процедура, которая также считает страховку для каждого товара
CREATE PROCEDURE customerItemsWithInsurance(IN custid varchar(12), rate XML)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c_cur CURSOR WITH RETURN FOR

values(xmlquery('

for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

let $items:=(
<Items>{
for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer

let $insurance:=<insurance currency="{($rate//rate[@price=""]/@currency)}">
{(
if($Customer0/Item/@price > 500) then (
$Customer0/Item/@price * $rate//rate[@price=""]/@rate
)
else (

if($Customer0/Item/@price > 100) then (
$Customer0/Item/@price * $rate//rate[@price="500"]/@rate
)
else (
$Customer0/Item/@price * $rate//rate[@price="100"]/@rate
)

)
)}</insurance>

where $Customer0/@customerid= $Customer/@customerid

return
transform
copy $item:=$Customer0/Item
modify( do insert $insurance as last into $item)
return $item
}</Items>
)

where $Customer/@customerid= $customerid

return
transform
copy $cust:=$Customer
modify(do insert $items as last into $cust)
return $cust

' passing custid as "customerid", rate as "rate" ));

OPEN c_cur;
END

В вызове к хранимой процедуре задаются два параметра времени исполнения - customerid и insurance XML.

call customerItemsWithInsurance(?,?)

Из предыдущего примера видно, что если данные, с которыми мы работаем в базе данных, находятся в XML-формате, то можно применить мощь языка XQuery для реализации более сложной бизнес-логики, чем позволял SQL сам по себе. Понятно также, что XML, который мы использовали в запросе, не обязательно должен находиться в базе данных. Таким образом, XML-данные, участвующие в SQL/XML-запросе, либо могут храниться в базе данных в "чистой" (иерархической) форме, которую можно создать с помощью SQL/XML-функций, либо эти данные можно передавать как параметры времени исполнения запроса. Границы между базой данных и сервером приложения понемногу стираются.


За и против

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

  1. Несмотря на улучшения, производительность до сих пор меньше, чем для реляционных данных.
  2. XQuery - новый язык, и некоторые SQL/XML- функции имеют такой синтаксис, к которому еще надо привыкнуть.
  3. В настоящее время большинство имеющихся данных хранятся в реляционном формате.
  4. Наиболее важным является то, что мы создаем новый способ задания бизнес-приложений и схем данных, который отличается от подхода, используемого в настоящее время, - объектно-ориентированных приложений и нормализованных реляционных схем.
  5. У нас есть не так много инструментов, которые могли бы отлаживать и оптимизировать такие запросы для улучшения производительности.

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

  • Независимо от языка внешнего кода (Java, XQuery, JavaScript, PHP) язык для навигации по XML-документу всегда один (XPath) на всех уровнях.
  • Хотя существующие данные являются реляционными, их можно легко запрашивать и преобразовывать к формату XML с помощью некоторых новых SQL/XML-функций, представленных в Viper II . Посмотрите на пример из первой статьи: "Второй случай - Все данные хранятся в реляционной базе данных". Запрос можно упростить, используя новую функцию XMLROW, представленную в Viper 2.
    Select XMLROW (customerid, firstname, lastname OPTION as attributes ROW Customer)
    from customer_table where customerid=?

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

    Рисунок 5. Соединение реляционных и XML-столбцов
    Соединение реляционных и XML-столбцов
    Select details, weight from SQLPRODUCT, ITEM_TABLE
    where xmlexists ('$itemxml/item[@itemid=$pid]'
    passing ITEM_TABLE.ITEMXML AS "itemxml", SQLPRODUCT.PID AS "pid" )

    В DB2 9 вы можете передавать параметры времени исполнения запросу XQuery, вставленному в выражение SQL, с помощью оператора passing, но не можете сделать то же самое для SQL, вставленного в выражение XQuery. В Viper 2 такого ограничения нет, и вы можете передавать параметры времени исполнения в реляционные запросы внутри XQuery.

    Листинг 7. Передача параметров времени исполнения SQL, вставленному в выражение XQuery
    values(xmlquery('
    
    for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
    where $Customer0/@customerid= $custid
    return (
    $Customer0/Item,
    db2-fn:sqlquery(
    ''select xmlrow(details, description, weight option ROW "description")
    from sqlproduct where pid= parameter(1)'', $Customer0/Item/@ID))
    
    ' passing cast( ? AS varchar(255) ) as "custid" ))

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

  • Во многих случаях даже производительность не будет большой проблемой, так как:
    • Вы можете создавать основанные на выражениях XPath индексы для XML-документов, хранящихся в базе данных.
      create index custfname on customer_table(info) generate key
      using xmlpattern '/Customer/@firstname' as sql varchar(64)
    • Количество необходимых соединений уменьшается, потому что схемы баз данных становятся проще.
    • Можно сократить ввод/вывод, так как вы можете управлять данными в запросе еще до того, как отправите их в приложение.
    • Вы всегда можете извлечь информацию о ключах из XML-документа в реляционные столбцы с помощью SQL/XML-функций, таких как XMLTable, и создать для них реляционные индексы.
    • Для XML-документов можно создавать индексы текстового поиска.

Заключение

XML вошел в нашу жизнь надолго и всерьез. Большинство индустриальных и правительственных организаций стандартизируют свои XML-схемы и настаивают на использовании электронных документов, соответствующих этим схемам. Учитывая, что обмен данными B2B (бизнес-бизнес) по сети совершается в формате XML, почему бы не хранить эти данные в том же формате (pureXML) в базе данных? Имея данные в XML, вы можете индексировать их, запрашивать, проверять на соответствие схеме, управлять ими, преобразовывать и обновлять их с помощью языка XQuery и стандартного SQL/XML. По мере того как вы переносите в запросы логику приложения, ваша база данных становится активным действующим лицом в мире сервис-ориентированной архитектуры, публикуя хранимые процедуры как Web-сервисы и XML-каналы.

"Старый порядок вещей уходит, давая место новому." Смерть Артура

Ресурсы

Научиться

Получить продукты и технологии

  • Загрузите открытую бета-версию DB2 Viper 2.
  • Используйте в своем следующем проекте ознакомительное ПО IBM, которое можно загрузить прямо с сайта developerWorks. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Information Management, XML, SOA и web-сервисы
ArticleID=388987
ArticleTitle=Программирование на XML для DB2: Часть 2. Использование встроенной в базу данных поддержки XML в вашей архитектуре приложений
publish-date=05132009