RESOURCE
and CONNECT
ролями. Под тестовым пользователем необходимо выполнить
скрипт /src/sqlscripts/prepareSchema.sql
, который создаст таблицы, загрузит тестовые данные,
создаст пакеты и объектные типы.
tutor.model
- модель данных - классы-сущности,
tutor.proxies
- интерфейсы-прокси для оракловых объектов (пакетов/типов),
tutor.cases
- выполняемые тестовые примеры, которые описываются в этом руководстве.
Каждый параграф в этом документе соответствует одному тестовому примеру, например,
Простой вызов хранимой процедуры - tutor.cases.Case1
.
ojdbc14.jar,orai18n.jar
- oracle jdbc драйвер и классы, поддерживающие
интернализацию oracle объектных типов и коллекций, последние версии можно скачать
с официального сайта oracle
JDBC, SQLJ, Oracle JPublisher and Universal Connection Pool (UCP).
xml-apis.jar
- xerces xml парсер
The Apache Xerces Project.
/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
задают маппинги
используемые в нашем тестовом приложении.
В первом примере выполним несколько простых вызовов хранимых процедур. Тестируемый пакет:
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>>
PRICE
таблицы BOOK
.
double bookPrice = packageInstance.getBookPrice(68l);
packageInstance.updateBookPrice(68l, new Double(1000.0));
double newBookPrice = packageInstance.getBookPrice(68l);
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>
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);
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>
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>
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
с точки зрения полноты передаваемых экземпляров объектов.
IN
(по умолчанию), OUT
и IN/OUT
.
С режимом IN
все понятно - аргумент просто передается.
С OUT
возможны разные случаи:
null
- он будет также проигнорирован.
null
- то его значение будет обновлено
в соответсвии с подходящим для этого класса маппингом.
IN/OUT
- это объединение IN
и OUT
, правила те же.
tutor.cases.Case4
класса является вызов процедуры
PKG_BOOKS_API.formatBookIsbns
. Данная процедура проходит по списку книг - экземпляров
объектного типа TBOOK
и обновляет значение поля ISBN
вместе
с соответствующим обновлением записи в таблице.
Аргумент передается из java как IN/OUT
, то есть обновленный список будет
возвращен назад java-клиенту. Как он будет обработан?
В маппинге аргумента процедуры есть атрибут
collection-merging
, который определяет каким образом
исходная коллекция будет обработана после возвращения
OUT
аргумента со стороны oracle.
Возможные значения атрибута: "reload"
: исходная коллекция очищается и заполняется значениями возвращаемой коллекции.
"append"
: значения возвращаемой коллекции добавляются к исходной коллекции.
"update"
: значения в исходной коллекции обновляются подходящими значения из возвращаемой коллекции (если найдутся).
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-типа
экземпляр класса. С сигнатурой методов можно ознакомиться в документации.