Oracle-magpie руководство пользователя


0. Настройка окружения и конфигурация библиотеки

Руководство показывает возможности библиотеки на простейшем приложении типа веб-магазин по продаже книг. Перед изучением демонстрационных примеров необходимо установить тестовое окружение.

Установка oracle-схемы

Для успешного выполнения демонстрационных примеров требуется тестовая oracle-схема, с делегированными RESOURCE and CONNECT ролями. Под тестовым пользователем необходимо выполнить скрипт /src/sqlscripts/prepareSchema.sql, который создаст таблицы, загрузит тестовые данные, создаст пакеты и объектные типы.

Описание клиентского java-приложения

Исходники примеров включают в себя три пакета: tutor.model - модель данных - классы-сущности, tutor.proxies - интерфейсы-прокси для оракловых объектов (пакетов/типов), tutor.cases - выполняемые тестовые примеры, которые описываются в этом руководстве. Каждый параграф в этом документе соответствует одному тестовому примеру, например,
Простой вызов хранимой процедуры - tutor.cases.Case1.

Подключаемые java библиотеки

Oracle-magpie использует другие .jar файлы:

Файл конфигурации библиотеки

В основном xml-файле конфигурации библиотеки задается data-source, указывающий на схему, к которой будут выполняться подключения, пути к остальным конфигурационным файлам, а также перечисляются классы, маппинги к которым заданы с помощью аннотаций: /src/resources/tutor-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
  <data-source class="oracle.jdbc.pool.OracleDataSource"
    url="jdbc:oracle:thin:@127.0.0.1:1521:db102"
    user="bookstore" password="bookstore">
  </data-source>
  <mappings>
    <file name="./bin/resources/default-mappings.xml"/>
    <file name="./bin/resources/class-mappings.xml"/>
    <file name="./bin/resources/object-mappings.xml"/>
  </mappings>
</config>

В файле default-mappings.xml заданы предопределенные маппинги основных скалярных java-oracle типов (как пример int, Long, VARCHAR2, NUMBER,..), .
Файлы class-mappings.xml and object-mappings.xml задают маппинги используемые в нашем тестовом приложении.

1. Простой вызов хранимой процедуры


В первом примере выполним несколько простых вызовов хранимых процедур. Тестируемый пакет: PKG_BOOKS_API, java class: tutor.cases.Case1. Перед выполнением обращений к oracle необходимо инициализирвоать библиотеку одним из конструкторов Configuration класса, к примеру:

Configuration config = new Configuration("./bin/resources/tutor-config.xml");
((OracleDataSource)config.getDataSource()).setConnectionCachingEnabled(true);
Второй строчкой включается пул соединений, рекомендуется это делать всегда, по той причине, что после каждого вызова библиотека закрывает соединение, при включенном пуле соединение будет не закрываться, а возвращаться в пул. Следующим шагом мы получаем экземпляр прокси для PKG_BOOKS_API пакета:
PKG_BOOKS_API packageInstance = (PKG_BOOKS_API) config.getPackageProxy(PKG_BOOKS_API.class);
Класс PKG_BOOKS_API это java интерфейс, методы которого являются заглушками к соответствующим процедурам в пакете PKG_BOOKS_API (имена выбраны одинаковыми, но это необязательно).

Замечание: Все методы интерфейса PKG_BOOKS_API могут выбрасывать два проверяемых исключения: SQLException и DBObjectConversionException. Рекомендуется (но не требуется) всегда объявлять эти исключения в методах ваших прокси-интерфейсов.

Описание свойств данного прокси задается в конфигурационном файле object-mappings.xml посредством маппинга:
<db-object object-is="package" class="tutor.proxies.PKG_BOOKS_API" name="PKG_BOOKS_API">

  <procedure java-name="updateBookPrice" db-name="updateBookPrice">
    <argument position="0" db-name="bookId" class="java.lang.Long"/>
    <argument position="1" db-name="newPrice" class="java.lang.Double"/>
  </procedure>

  <procedure java-name="getBookPrice" db-name="getBookPrice">
    <return class="double" />
    <argument position="0" db-name="bookId" class="java.lang.Long"/>
  </procedure>

   .......

</db-object>>

После получения экземпляра прокси можно попробовать вызвать методы интерфейса и, соответственно, проверить правильность вызова хранимых процедур. Содержание кода процедур - простейший select и update столбца PRICE таблицы BOOK.
double bookPrice = packageInstance.getBookPrice(68l);
packageInstance.updateBookPrice(68l, new Double(1000.0));
double newBookPrice = packageInstance.getBookPrice(68l);

2. Маппинг класса-сущности и вызов методов объектного типа oracle

Данный пример описывает как задается маппинг java класса на объектный тип oracle, а также вызов методов объектного типа. Объектные типы oracle - это объектно-ориентированная надстройка над pl/sql, которая по сути является объектно-ориентированным языком с некоторыми ограничениями. Более полную информация можно найти на официальном сайте oracle:
Oracle® Database Object-Relational Developer's Guide. Для того, чтобы экземляр java класса-сущности можно было использовать в качестве аргумента при вызове хранимой процедуры необходимо задать маппинг между этим классом и подходящим объектным типом в oracle. Например, для класса tutor.model.Book:
public class Book {
  Long bookId;
  String title;
  String isbn;
  String annotation;
  String publicationDate;
  int pages;
  String publisher;
  Double price;
  List<BookReview> reviews;
  List<String> authors;
  .....
}
и объектного типа TBOOK:
CREATE OR REPLACE TYPE TBOOK AS OBJECT
(
  bookId number,
  title varchar2(4000),
  isbn varchar(50),
  annotation CLOB,
  publicationDate varchar2(20),
  pages integer,
  publisher varchar2(1000),
  price binary_double,
  reviews tab_reviews,
  authors TAB_VARCHAR2,
  member procedure save,
  ....
) /
можно задать такой маппинг (см. файл class-mappings.xml):
  <class-mapping class="tutor.model.Book" db-type="TBOOK">
   <field name="bookId" db-name="BOOKID" />
   <field name="title" db-name="TITLE" />
   <field name="isbn" db-name="ISBN" />
   <field name="authors" db-name="AUTHORS" element-class="java.lang.String" />
   <field name="reviews" db-name="REVIEWS"
    element-class="tutor.model.BookReview" />
   <field name="publisher" db-name="PUBLISHER" />
   <field name="publicationDate" db-name="PUBLICATIONDATE" />
   <field name="pages" db-name="PAGES" />
   <field name="annotation" db-name="ANNOTATION" />
   <field name="price" db-name="PRICE" />
  </class-mapping>
Замечания:
1. Значеие атрибута "class" должно быть полным именем класса.
2. Для классов-коллекций задание значение атрибута "element-class" обязательно.
3. Класс должен иметь правильно определенные public getter и setter методы для всех участвующих в маппинге полей.

После задания этого маппинга класс tutor.model.Book можно использовать. В примере tutor.model.Case2 первые строки кода аналогичны предыдущему примеру. Далее, создаем тестовый экземпляр класса tutor.model.Book, и следующим шагом запрашиваем прокси объектного типа для этого экземпляра:
TYPE_BOOK dbProxy = (TYPE_BOOK) config.getObjectTypeProxy(TYPE_BOOK.class, book);
book.setDbProxy(dbProxy);
После получения прокси можем выполнять удаленные вызовы методов объектного типа:
book.save();
....
book.addReview(review);
В ходе этих вызовов magpie библиотека автоматически передает экземпляр класса tutor.model.Book в Oracle, автоматически преобразуя его в экземпляр TYPE_BOOK типа, вызывает указанные методы, возращает java-клиенту и обновляет поля исходного экземпляра класса новыми значениями. Маппинг методов объектного типа oracle на java интерфейс аналогичен маппингу пакета за исключением атрибута object-is элемента db-object, который должен иметь значение "type", а также атрибута base-class - его значение - имя класса-сущности, имеющего мэппинг с этим объектным типом (см.выше).
<db-object object-is="type" class="tutor.proxies.TYPE_BOOK"
    name="TBOOK" base-class="tutor.model.Book">

   <procedure java-name="save" db-name="save">
   </procedure>

   ....

   <procedure java-name="addReview" db-name="addReview">
      <argument position="0" class="tutor.model.BookReview" db-name="newReview"/>
   </procedure>

</db-object>

3. Различные вызовы хранимых процедур и настройка передаваемых аргументов

Третий пример демонстрирует возможности настройки при использовании библиотеки. В ходе выполнение метода tutor.cases.Case3.main() мы запрашиваем список покупателей из базы вызовом процедуры PKG_BOOKS_API.getCustomersList. Далее, в "for" цикле ищем покупателя с максимальной суммой по всем заказам, что реализовано методом calculateTotalOrderedSum объектного типа TCUSTOMER. В самом методе содержится простейшй запрос:
select nvl(sum(o.sum),0) into result from orders o where customer_id=self.customerId;
Для корректного выполнения этого метода нам достаточно иметь значение поля customerId и нет необходимости передавать все значение экземпляра объекта в oracle и обратно. В реальном приложении, если в подобном случае мы будем передавать полный экземпляр объекта, возможно заметное снижение производительности. Очевидно, чтобы избежать этого, необходимо ограничить "заполнение" экземпляра объекта только необходимыми в контексте данного метода полями. Библиотека предоставляет такую возможность при помощи элементов property-group в описании маппинга класса. Например, в маппинге Customer класса в файле class-mappings.xml это задано так:
<class-mapping class="tutor.model.Customer" db-type="TCUSTOMER">
  <field name="customerId" db-name="customerId" />

  ....

  <property-group name="withoutOrders" behaviour="skip">
    <property name="orders"/>
  </property-group>

  <property-group name="empty" behaviour="pass">
  </property-group>
</class-mapping>
В маппинге заданы две property-group-ы, атрибут behaviour служит предикатом вхождения, то есть значение "pass" декларирует, что все пересчиленные поля, входящие в эту группу, будут передаваться, а значение "skip" - наоборот, - будут передавать все поля, за исключением перечисленных в группе. Например, "withoutOrders" property-group - передаются все поля за исключением списка orders ArrayList, а при использовании "empty" property-group не передается ни одного поля. Использование выбранной группы в конкретном методе задается в маппинге этого метода, например calculateTotalOrderedSum маппинг:
  <procedure java-name="calculateTotalOrderedSum" db-name="calculateTotalOrderedSum"
     in-group="withoutOrders" out-group="empty">
    <return class="java.lang.Double"/>
  </procedure>


Замечания:
in-group атрибут используется для задания полей экземпляра объекта, которые будут передаваться в oracle при вызове этого метода;
out-group атрибут используется для задания атрибутов, которые будут обновлены атрибутами объекта возвращаемого oracle.

Дальнейший код tutor.cases.Case3.main() тестирует разные способы вызова метода TCUSTOMER.updateCustomerInfo с точки зрения полноты передаваемых экземпляров объектов.
Замечание: библиотека позволяет задавать маппинги разных методов на одну и ту же хранимую процедуру.

4. Работа с коллекциями и OUT аргументами

Четвертый пример обращает внимание на тип передачи аргументов хранимых процедур, он может принимать одно из трех значений: IN (по умолчанию), OUT и IN/OUT. С режимом IN все понятно - аргумент просто передается. С OUT возможны разные случаи:
1. Если java аргумент метода является примитивом он будет проигнорирован.
2. Если значения этого аргумента в конкретном вызове равно null - он будет также проигнорирован.
3. Если аргумент не примитив, и значение не null - то его значение будет обновлено в соответсвии с подходящим для этого класса маппингом.
Режим IN/OUT - это объединение IN и OUT, правила те же.

Ключевым моментом в коде tutor.cases.Case4 класса является вызов процедуры PKG_BOOKS_API.formatBookIsbns. Данная процедура проходит по списку книг - экземпляров объектного типа TBOOK и обновляет значение поля ISBN вместе с соответствующим обновлением записи в таблице. Аргумент передается из java как IN/OUT, то есть обновленный список будет возвращен назад java-клиенту. Как он будет обработан? В маппинге аргумента процедуры есть атрибут collection-merging, который определяет каким образом исходная коллекция будет обработана после возвращения OUT аргумента со стороны oracle. Возможные значения атрибута:
1. "reload": исходная коллекция очищается и заполняется значениями возвращаемой коллекции.
2. "append": значения возвращаемой коллекции добавляются к исходной коллекции.
3. "update": значения в исходной коллекции обновляются подходящими значения из возвращаемой коллекции (если найдутся).

5. Маппинг на скалярный тип (маппинг-значение)

Библиотека Oracle-magpie дает возможность задавать маппинги не только на объектные типы oracle, но и на простые скалярные sql-типы как NUMBER,VARCHAR2,DATE и пр. В файле default-mappings.xml заданы предопределенные маппинги основных скалярных java-oracle типов. Пятый пример руководства демонстрирует как задать маппинг java enum tutor.model.OrderState к NUMBER sql типу. Маппинг определен в class-mappings.xml файле строчками:
<value-mapping class="tutor.model.OrderState">
  <sql-type name="NUMBER"
  to-oracle-method="tutor.model.OrderState.toNumber"
  from-oracle-method="tutor.model.OrderState.fromNumber" />
</value-mapping>
Атрибуты to-oracle-method и from-oracle-method указывают на пользовательские статические методы, конвертирующие экземпляр указанного класса в подходящий для данного sql-типа экземпляр класса. С сигнатурой методов можно ознакомиться в документации.