Создание высокопроизводительных Java-приложений для доступа к данным: Часть 3. Практические приемы работы с API pureQuery в Data Studio

Обучение приемам работы с pureQuery на примерах кода и реальных сценариях.

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

Витор Родригес, разработчик программного обеспечения, IBM

Витор Родригес фотоВитор Родригес (Vitor Rodrigues) разрабатывает программное обеспечение в группе IBM Data Studio Developer лаборатории IBM Silicon Valley Lab. Он окончил Университет Мино в Португалии по компьютерной технике и системотехнике. Витор работает в IBM с 2005 года, и сначала был практикантом по DB2 Everyplace и DB2 9 pureXML. До прихода в группу разработчиков Data Studio он был сотрудником группы технической поддержки DB2 pureXML и IBM Data Studio, работал в лабораториях IBM в Торонто и Кремниевой Долине.



04.06.2010

Введение

Полезные статьи по pureQuery

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

Выбор стиля: встроенные или аннотированные методы

В предыдущих статьях этой серии авторы представили два стиля программирования, доступных в pureQuery: на основе аннотированных и встроенных методов.

Оба стиля имеют свои преимущества, и при выборе того или иного стиля нужно руководствоваться следующими рекомендациями:

Применяйте стиль pureQuery на основе аннотированных методов, если вы:

  • хотите использовать статический SQL во время исполнения приложения;
  • предпочитаете отделять SQL от бизнес-логики;
  • хотите получить простой код уровня доступа к данным, сгенерированный инструментарием Data Studio;
  • любите использовать XML-файлы для определения уровня доступа к данным;
  • имеете готовые запросы.

Применяйте стиль на основе встроенных методов pureQuery, если вы:

  • любите, чтобы операторы SQL были встроены в код Java, как при обычном программировании JDBC;
  • имеете динамически генерируемые запросы.

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

Обращение к коллекциям

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

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

Сценарий: отображение каталога изделий

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

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

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

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

Листинг 1. Обращение к существующему набору результатов в памяти
public void displayProducts(String category){
	Data db = DataFactory.getData(getConnection());
	List<Product> catalog = db.queryList("SELECT PID, NAME, DETAILS, " 
		+ " PRICE, WEIGHT, CATEGORY, BRAND, SIZE, "
        + " DESCRIPTION, BESTSELLER FROM PDQ_SC.PRODUCT "  
        + " where CATEGORY = ?", Product.class, category);
	for (Product p : catalog){
		//list product on webpage
	}
	Data inMemData = DataFactory.getData();
	List<Product> bestsellers = inMemData.queryList("SELECT * FROM " 
		+ " ?1.com.pureQuery.Product as prod WHERE prod.bestseller = 'Y'", 
		Product.class, catalog);
	for (Product p : bestsellers){
		//list bestseller
	}	
}

В строке 7 листинга 1 оценивается запрос SQL к Java-коллекции catalog, где содержатся все изделия определенной марки. Обратите внимание на то, что для обращения к этой Java-коллекции используется тот же API, что и для обращения к базе данных. Если вы прочли Часть 2 этой серии статей, вы должны быть знакомы с методом API queryList. Обратите также внимание на квалификационное имя класса в операторе SQL. Так как запросы используют API на основе встроенных методов, API оценивается только во время прогона, поэтому нужно указать полное квалификационное имя класса, чтобы при наборе запроса SQL получить контент-подсказку инструментария pureQuery и чтобы механизм AI знал, какие используются типы объектов.

Сценарий: создание отгрузочной ведомости

Рассмотрим склад торговой фирмы. После подтверждения платежа на склад передается распоряжение на отгрузку товаров по этому заказу. На складе есть управляющее ПО, которое получает идентификатор заказа и использует эту информацию для обращения к таблице ORDER_ITEMS, чтобы найти товары, входящие в заказ, и отгрузить их заказчику. Узнав содержимое заказа, ПО генерирует список, содержащий наименования товаров и их местонахождение (проход и ячейка), чтобы работники склада могли достать их и сформировать заказ. Так как информация о местонахождении используется часто, она хранится в памяти приложения в форме объектов locator. Следующий фрагмент кода (листинг 2) показывает, как соединить информацию о товарах и о местонахождении, чтобы сгенерировать отгрузочную ведомость для работников склада:

Листинг 2. Соединение двух коллекций из памяти с использованием API pureQuery
public List<ProductInfo> generateShippingReport(String orderID){
	Data db = DataFactory.getData(getConnection());
	List<Locator> locators = LocatorUtil.getLocators();
	Iterator<Product> products = db.queryIterator("SELECT p.* from PRODUCT AS p, " 
	+ " ORDER_ITEMS AS po where p.pid = po.pid and po.poid = ?", 
	Product.class, orderID);
	Data inMemData = DataFactory.getData();
	List<ProductInfo> shippingReport = inMemData.queryList("SELECT pr.pid, "  
		+ " pr.name, lr.aisle, lr.bin FROM ?1.com.pureQuery.Product AS pr, " 
		+ " ?2.com.pureQuery.Locator AS lr where pr.pid = lr.pid",   
		ProductInfo.class, products, locators);
	return shippingReport;
}

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

После исполнения оператора join shippingReport будет содержать объединенную информацию из коллекций locators и products.

Подключаемый механизм обратных вызовов с применением ловушки

Интерфейс pureQuery Data позволяет добавлять к своим соединениям ловушки операторов. По функциональности ловушка (hook) аналогична триггеру базы данных. Она предоставляет возможность определить функциональность, которая выполняется до и/или после каждого обращения к API. Эту возможность можно использовать несколькими способами:

  • мониторинг производительности: ловушки можно использовать для измерения параметров исполнения вызовов API, таких как время исполнения, параметры сети и параметры ввода-вывода;
  • проверка данных: ловушки операторов позволяют проверять данные параметра до исполнения оператора, предоставляя возможность выполнять проверку ограничений и правильности данных на уровне приложения;
  • контроль SQL: если нужно контролировать все операторы SQL, исполняемые приложением pureQuery, ловушки позволяют легко это делать.

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

Сценарий: реализация монитора производительности

Рассмотрим, как использовать ловушку для реализации функции контроля производительности приложения.

Первым делом определим класс, который реализует интерфейс pureQuery Hook. Используемый для этого код приведен в листинге 3.

Листинг 3. Ловушка SystemMonitorHook для контроля скорости доступа к базе данных
public class SystemMonitorHook implements Hook
{
  DB2SystemMonitor systemMonitor;

  public void pre (String methodName, Data dataInstance, 
  	SqlStatementType sqlStatementType, Object... parameters)
  {
    try {
     systemMonitor = ((DB2Connection)dataInstance.getConnection()).getDB2SystemMonitor();
     systemMonitor.enable (true);
     systemMonitor.start (DB2SystemMonitor.ACCUMULATE_TIMES);
    }
    catch (SQLException e) {
      throw new RuntimeException ("Unable to start system monitor " + e.getMessage ());
    }
  }

  public void post (String methodName, Data dataInstance, Object returnValue, 
  	SqlStatementType sqlStatementType, Object... parameters)
  {
    try {
      systemMonitor.stop ();
      System.out.println("Performance of method: " + methodName  + ":");
      System.out.println ("Application Time " + 
      	systemMonitor.getApplicationTimeMillis () + "milliseconds");
      System.out.println ("Core Driver Time " + 
      	systemMonitor.getCoreDriverTimeMicros () + "microseconds");
      System.out.println ("Network Time " + 
      	systemMonitor.getNetworkIOTimeMicros () + "microseconds");
      System.out.println ("server Time " + 
      	systemMonitor.getServerTimeMicros() + "microseconds");      
    }
    catch (SQLException e) {
      throw new RuntimeException
      		("Unable to stop system monitor " + e.getMessage ());
    }
  }

}

Класс SystemMonitorHook реализует интерфейс pureQuery Hook, который декларирует методы pre() и post(). Эти два метода исполняются до и после вызова API pureQuery. В листинге 3 используются элементы специального API драйвера IBM JCC. Подробнее об этом API можно узнать на странице DB2 Information Center, посвященной IBM Data Server для JDBC.

Эту ловушку могут использовать любые из наших приложений pureQuery. Чтобы запустить ее, достаточно подключить ловушку к экземпляру интерфейса Data, как показано в листинге 4.

Листинг 4. Подключение ловушки к соединению
//...
Connection con = getConnection();
SystemMonitorHook monitorHook = new SystemMonitorHook();
Data data = DataFactory.getData(CustomerData.class, con, monitorHook); 
// ...

data.queryList("select * from pdq_sc.product", Product.class);

// ...

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

Результат вызова pureQuery из листинга 4 показан на рисунке 1.

Рисунок 1. Результат работы ловушки системного монитора
Результат работы ловушки системного монитора

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

Сравнение методов queryList и queryArray с методом queryIterator

pureQuery предоставляет три метода API, которые возвращают коллекции Java-объектов: queryArray, queryList и queryIterator. Чтобы избежать операций преобразования типов, нужно использовать тот метод, который лучше всего соответствует типу коллекции, ожидаемому приложением.

Однако эти методы не только возвращают разные типы. Способ их работы тоже имеет значение и должен приниматься во внимание при разработке приложения. Если методы queryArray и queryList выполняют интенсивную материализацию набора результатов, то queryIterator реализует слабую материализацию, выдавая данные по требованию, когда вызывается метод Iterator.next().

При выборе одного из методов учтите следующие рекомендации.

1. Используйте queryArray или queryList, если:

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

2. Используйте queryIterator, если:

  • вы предпочитаете разбивать результаты на страницы: данные выводятся по требованию при отображении новых страниц;
  • у приложения ограниченный объем доступной памяти.

Использование возможностей пакетной обработки pureQuery

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

Однородное пакетное редактирование

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

Чтобы выполнить требования по пакетному редактированию при помощи простого в применении API, стиль на основе встроенных методов pureQuery предлагает метод updateMany. Его можно применять для однородной пакетной обработки, и ему нужны только два параметра: оператор SQL update и коллекция объектов Java, к которой будет обращаться вызов update. За кулисами updateMany реализует практические рекомендации по пакетной обработке JDBC, существенно уменьшая число сетевых ловушек (trips), требуемых для редактирования данных. Он гарантирует также, что все операции редактирования выполняются в рамках одной транзакции.

Сценарий: редактирование базы данных изделий

Каждую неделю центральное приложение несколько раз редактируется партнерами, которые добавляют перечень новых изделий, выставляемых ими на продажу. Приложение должно редактировать базу данных через ПО онлайнового магазина, чтобы эти новые изделия попадали в каталог, просматриваемый пользователями. Самый простой и быстрый способ сделать это – воспользоваться методом API updateMany(), как показано в листинге 5.

Листинг 5. Вызов API однородной пакетной обработки для редактирования
//...
Data db = DataFactory.getData(getConnection());
List<Product> prods = getNewProducts();
db.updateMany("INSERT INTO PRODUCT (PRODUCTID, NAME, DETAILS, LISTPRICE,"
		+ " WEIGHT, CATEGORY, BRAND, SIZE, DESCRIPTION)"
		+ " VALUES (:productid, :name, :details, :listprice, :weight, :category,"
		+ " :brand, :size, :description)", prods);

Заметьте, что для редактирования нескольких строк в таблице базы данных достаточно единственного вызова API. pureQuery последовательно вставляет новые изделия, содержащиеся в переменной prods.

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

Неоднородное пакетное редактирование

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

Сценарий: редактирование заказов

Рассмотрим сценарий, когда заказ покупателя хранится в двух таблицах: списке заказов ORDER и списке товаров по каждому заказу ORDER_ITEMS. Стандарт SQL не позволяет производить операции вставки/редактирования/удаления для нескольких таблиц посредством одного оператора, поэтому приложения должны выполнять два отдельных оператора, по одному для каждой таблицы. Даже при использовании однородной пакетной обработки для редактирования таблицы ORDER_ITEMS приложение все равно должно производить два отдельных обращения по сети – для редактирования таблицы ORDER и для редактирования таблицы ORDER_ITEMS. К тому же вы должны сами управлять транзакцией, чтобы после редактирования обеих таблиц база данных оставалась согласованной.

На рисунке 2 показана взаимосвязь между таблицами ORDER и ORDER_ITEMS.

Рисунок 2. Отношение "один ко многим" между таблицами ORDER и ORDER_ITEMS
Взаимосвязь между таблицами ORDER и ORDER_ITEMS

В версии pureQuery 1.2 добавлена поддержка неоднородных пакетных исправлений. Она позволяет объединять несколько операторов SQL с маркерами параметров или без них в одно обращение через сеть. В этом сценарии можо использовать неоднородную пакетную обработку для редактирования таблиц ORDER и ORDER_ITEMS в одной операции с базой данных. Если JDBC поддерживает только пакетную обработку операторов с литералами, то функция неоднородной пакетной обработки pureQuery допускает пакетную обработку операторов с параметрами. Это создает такие возможности, как многократное использование пути доступа и предотвращение инжекции SQL.

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

В листинге 6 неоднородная пакетная обработка pureQuery используется для редактирования нескольких таблиц с выполнением нескольких обращений к интерфейсу OrderData.

Листинг 6. Неоднородная пакетная обработка с использованием API pureQuery
public void inserPurchaseOrder(PurchaseOrder po, String poid){
	OrderData orderData = DataFactory.getData(OrderData.class, getConnection());
	//start batch
	((Data)orderData).startBatch(HeterogeneousBatchKind.heterogeneousModify__);
	//create new order
	orderData.insertNewPurchaseOrder(po);
	//add items to order
	for (OrderItem oi : po.getItems())
	{
		orderData.addItemToPurchaseOrder(poid, oi);
	}
	// end batch
	((Data)orderData).endBatch();
}

Обратите внимание на то, что методы startBatch() и endBatch() относятся к интерфейсу com.ibm.pdq.runtime.Data, так что прежде чем мы сможем вызывать эти методы, объект OrderData нужно согласовать с интерфейсом Data. Иначе, можно добавить интерфейс OrderData к интерфейсу Data, и тогда согласования не потребуется. Между методами startBatch() и endBatch() помещаются все вызовы интерфейсов, которые нужно выполнить в одной пакетной транзакции. В листинге 6 многократные вызовы API внутри пакетного блока гарантируют, что вся информация, относящаяся к заказу, будет отредактирована в рамках одной транзакции.

Однако неоднородная пакетная обработка pureQuery дает еще больше! Кроме поддержки исполнения нескольких операций в одном и том же объекте, пакетная обработка pureQuery поддерживает объединение операций из разных стилей API, позволяя смешивать стили на основе встроенных и аннотированных методов внутри одной пакетной операции. Предположим, что требуется повторно использовать приведенный выше пример с добавлением еще одной операции, которая редактирует список товаров, уменьшая количество изделий в каждой позиции заказа. Более того, допустим, что вы хотите выполнить эту операцию с применением стиля на основе встроенных методов, а включение заказа в базу данных – с применением стиля на основе аннотированных методов. В листинге 7 показан код, аналогичный тому, который должен использоваться для реализации этих изменений.

Листинг 7. Неоднородное пакетное редактирование с использованием разных объектов Data
public void inserPurchaseOrder(PurchaseOrder po, String poid){
	Data data = DataFactory.getData(getConnection());
	OrderData orderData 	= DataFactory.getData(OrderData.class, data);
	//start batch
	data.startBatch(HeterogeneousBatchKind.heterogeneousModify__);
	//create new order
	orderData.insertNewPurchaseOrder(po);
	//add items to order
	for (OrderItem oi : po.getItems())
	{
		orderData.addItemToPurchaseOrder(poid, oi);
	}
	//update inventory
	data.updateMany("UPDATE INVENTORY SET " +
			" QUANTITY = QUANTITY - 1 WHERE PRODUCTID = :pid", 
			po.getItems());
	// end batch
	data.endBatch();
}

Как видно из листинга 7, оба метода из объектов data и orderData вызываются внутри одной и той же пакетной операции. Хотя я и ссылаюсь на два разных объекта, эти обращения выполняются внутри одной пакетной операции, так как оба объекта указывают на один и тот же базовый объект Data (обратите внимание на второй параметр вызова DataFactory.getData()).

Листинг 8. Интерфейс OrderData
public interface OrderData {
	  //insert a new purchaseOrder
	@Update(sql = "insert into DB2ADMIN.ORDER(orderid, customerid, numberofitems, " +
	" numberofproducts, subtotaloforder, taxamount, totalamount, timestamp) " +
	" values(:orderid, :customerid, :numberofitems, :numberofproducts, " + 
	" :subtotaloforder, :taxamount, :totalamount, :timestamp)")
	void insertNewPurchaseOrder(PurchaseOrder po);
	  
	//add product to PurchaseOrder
	@Update(sql="insert into DB2ADMIN.ORDER_ITEMS(orderid, productid, quantity, cost)"
		+ " values(?1, ?2.pid, ?2.quantity, ?2.price)")
	void addItemToPurchaseOrder(String poID, OrderItem p )
}

Листинг 8 содержит методы интерфейса OrderData, детализирующие вызовы insertNewPurchaseOrder и addItemToPurchaseOrder. Заметьте, что в методе addItemToPurchaseOrder для указания того, какие переменные объекта po должны применяться в качестве значения параметра, используются именованные параметры. В методе addItemToPurchaseOrder для ссылки на значения параметров используется сочетание нумерованных и именованных параметров.

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

Настройка наборов результатов при помощи ResultHandlers и RowHandlers

pureQuery предоставляет некоторые базовые функции отображения «объект-таблица», которые могут оказаться очень полезными при разработке своего собственного уровня доступа к данным. Data Studio Developer помогает автоматизировать этот шаг, предоставляя инструменты для генерации компонентов Java, отображающих таблицы базы данных, способные повысить производительность труда разработчика.

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

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

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

Сценарий: вывод нескольких наборов результатов в формате HTML

Рассмотрим приложение, которое должно отображать несколько наборов результатов в формате HTML. Один способ автоматизировать решение этой задачи заключается в использовании обработчиков результатов pureQuery. Они преобразуют содержимое набора результатов в объект. В следующем примере обработчик результатов обрабатывает набор результатов и превращает его в страницу HTML, отображая содержимое набора результатов в виде таблицы.

Вот фрагмент моего класса HTMLHandler (листинг 9).

Листинг 9. Обработчик результатов, генерирующий страницы HTML
public class HTMLHandler implements ResultHandler<String>
{

  private DocumentBuilderFactory documentBuilderFactory_;
  private DocumentBuilder domBuilder_;
  private Transformer transformer_;

  public HTMLHandler ()
  {
	// ... initialize variables
  }

//...

  public String handle (ResultSet resultSet)
  {
    Document document = domBuilder_.newDocument ();

    // Create root element
    Element rootElement = document.createElement ("html");
    rootElement.setAttribute ("xmlns", "http://www.w3.org/TR/REC-html40");
    document.appendChild (rootElement);
    Element headElement = document.createElement ("head");
    rootElement.appendChild (headElement);
    Element titleElement = document.createElement ("title");
    titleElement.setTextContent ("HTML Table Printer");
    rootElement.appendChild (titleElement);
    Element bodyElement = document.createElement ("body");
    rootElement.appendChild (bodyElement);
    generatedTable (resultSet, bodyElement, document);
    return transformXML (document);
  }

  private void generatedTable (ResultSet resultSet, Element bodyElement, 
  		Document document)
  {
      ResultSetMetaData resultSetMetaData = resultSet.getMetaData ();
      int columnCount = resultSetMetaData.getColumnCount ();
      Element tableElement = document.createElement ("TABLE");
      tableElement.setAttribute ("border", "1");
      bodyElement.appendChild (tableElement);
      Element headerRowElement = document.createElement ("TR");
      tableElement.appendChild (headerRowElement);

      for (int index = 0; index < columnCount; index++) {
        Element headerElement = document.createElement ("TH");
        headerElement.setTextContent (resultSetMetaData.getColumnLabel (index + 1));
        headerRowElement.appendChild (headerElement);
      }
      while (resultSet.next ()) {
        Element rowElement = document.createElement ("TR");
        tableElement.appendChild (rowElement);
        for (int index = 0; index < columnCount; index++) {
          Element columnElement = document.createElement ("TD");
          columnElement.setTextContent (resultSet.getString (index + 1));
          tableElement.appendChild (columnElement);
        }
      }
  }

// ... 
}

Для простоты в листинге 9 приведена лишь часть файла HTMLHandler.java.

Чтобы использовать этот и другие обработчики результатов, API pureQuery предлагает метод query(). Этот метод принимает несколько параметров, включая оператор SQL и обработчик результатов, и возвращает объект родового типа T. Этот тип определяется типом времени исполнения T параметризованного интерфейса RowHandler<T>. В примере из листинга 10 HTMLHandler обрабатывает набор результатов и возвращает объект типа String, содержащий текстовое представление Web-страницы, содержащее список всех строк набора результатов.

Чтобы преобразовать результат запроса в страницу HTML, достаточно передать HTMLHandler вызову API:

Листинг 10. Передача обработчика результатов HTMLHandler вызову API
public String generateProductList(){
	Data db = DataFactory.getData(getConnection());
	String htmlpage =  db.query("SELECT * from PRODUCT", new HTMLHandler());
	return htmlpage;
}

Сценарий: управление адресами разных структур

В общем случае строка таблицы базы данных может содержать информацию из разных объектов Java-приложения. Рассмотрим таблицу ADDRESS, содержащую адреса моих клиентов. Хотя для хранения этой информации я использую только одну таблицу, в разных странах структура адресов различается. Часто это приводит к тому, что некоторые столбцы таблицы не используются, или в одном и том же столбце хранятся разные свойства. Например, штаты США и провинции Канады могут храниться в колонке STATE; однако в Java-комонентах нужно иметь отдельные переменные с именами state и province.

Давайте определим интерфейс Address. Приложение извлекает адреса клиентов из базы данных и распечатывает их в обычном формате для этикеток, которые можно наклеивать на коробки. Единственный метод, который нужно реализовать, это printableFormat(), который возвращает адрес в том виде, в каком он должен быть напечатан.

Листинг 11. Пример интерфейса Address
public interface Address {

	public String printableFormat();
	
}

Так как у нас есть заказчики из США и Канады, потребуются две реализации интерфейса Address (листинг 12).

Листинг 12. Java-классы USAddress и CANAddress
public class USAddress implements Address {

	protected String customerName;
	protected String street;
	protected String city;
	protected String state;
	protected String zipcode;	

	//...
}

public class CANAddress implements Address {
	protected String customerName;
	protected String street;
	protected String city;
	protected String province;
	protected String postalCode;

	//...
}

Во время исполнения RowHandler решает, какой объект подходит для вывода текущей строки набора результатов (листинг 13).

Листинг 13. Обработчик набора результатов
public class AddressHandler implements RowHandler<Address>  {
	public Address handle(ResultSet rs, Address object) throws SQLException {
		Address addr = null;
		if (rs.getString(3).equals("United States")){
			USAddress us = new USAddress();
			us.setCustomerName(rs.getString(2));
			us.setStreet(rs.getString(4));
			us.setCity(rs.getString(5));
			us.setState(rs.getString(6));
			us.setZipcode(rs.getString(7));
			addr = us;
		} else if (rs.getString(3).equals("Canada")){
			CANAddress can = new CANAddress();
			can.setCustomerName(rs.getString(2));
			can.setStreet(rs.getString(4));
			can.setCity(rs.getString(5));
			can.setProvince(rs.getString(6));
			can.setPostalCode(rs.getString(7));
			addr = can;
		}
		return addr;
	}
}

Вместо того чтобы иметь дело с двумя типами адресов, все объекты приложения надо рассматривать как объекты типа Address (листинг 14).

Листинг 14. Обработка набора результатов с применением AddressHandler
//...
Data db = DataFactory.getData(getConnection());
List<Address> addrs = db.queryList("SELECT * FROM CUSTOMERADDRESS", 
				new AddressHandler());
// process list of Address objects…

Для простоты в данном примере мы не показываем никаких обращений к методам интерфейса. В реальных приложениях интерфейс Address должен определять несколько методов для работы с адресами, независимо от того, какой это объект - USAddress или CANAddress.

Определение оптимальной детализации интерфейсов

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

  • Если уровень доступа к данным очень специфичен, т.е. содержит код доступа к данным, который используется только одним, разрабатываемым в данный момент приложением, то правильно будет собрать весь код доступа к данным в один интерфейс. При этом подходе у каждого приложения будет свой собственный интерфейс, что облегчает управление. (См. структурную схему на рисунке 3.)
Рисунок 3. Архитектура, зависящая от приложения
Архитектура, зависящая от приложения
  • С другой стороны, если вы разрабатываете код доступа к данным, который потребуется для разных приложений, нужно разделить этот код на логические блоки и создать интерфейс на основе стиля аннотированных методов для каждого логического блока (рисунок 4). При таком подходе ваши приложения смогут разделять общие интерфейсы доступа к данным, многократно используя код и уменьшая объем работы, необходимой для создания полного приложения.
Рисунок 4. Архитектура логического блока
Logical Unit architecture

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

  • Приложение редко обращается к единственной таблице, поэтому при таком подходе многие операции потребовали бы работы не с одним интерфейсом. Например, для записи заказа в базу данных нужно редактировать таблицы ORDER и ORDER_ITEMS, так что интерфейс OrderData, который работает с обеими таблицами, лучше, чем два отдельных интерфейса. То же справедливо для извлечения данных; каждый раз при извлечении данных из таблицы ORDER_ITEMS нужно получать информацию о заказе, так что придется обращаться и к таблице ORDER. Потребуются логические блоки для объединения данных в соответствующих объектах.
  • В pureQuery v1.1 каждый интерфейс Data скомпонован в отдельный пакет Static SQL. При наличии отдельного интерфейса для каждой таблицы, чтобы достичь одних и тех же результатов, потребуется больше операций по администрированию базы данных.
  • При использовании статического SQL способ компоновки SQL в пакеты определяется методами интерфейса; поэтому нужно будет решить, какую коллекцию SQL нужно использовать в своем пакете базы данных.
  • Если вы намерены использовать неоднородную пакетную обработку, то, чтобы упростить ее, лучше собрать операторы SQL, подлежащие обработке, в один интерфейс.

Разбивка на страницы при помощи обработчика Paging

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

pureQuery API содержит обработчик результатов IteratorPagingResultHandler. Этому обработчику, как и любому другому ResultHandler, методы объекта Data передаются по запросу API. Конструктор этого обработчика позволяет определить несколько опций, включая класс компонентов, возвращаемых из набора результатов, или даже a RowHandler для управления каждой из возвращаемых строк. Кроме того, как именно должны возвращаться данные, можно указать также количество и тип данных, извлекаемых из базы данных, в соответствии с двумя разными схемами:

  • Извлечение блока: извлекаются все строки в интервале, определенном параметрами absoluteStartingRow и absoluteEndingRow.
  • Извлечение страницы: приложение определяет параметры pageSize и pageNumber; обработчик возвращает страницу данных с номером pageNumber, содержащую pageSize строк.

В листинге 15 приведен пример использования IteratorPagingResultHandler.

Листинг 15. Разбиение результатов на страницы с применением IteratorPagingResultHandler
//...
int pageNumber = this.getCurrentPage();
int pageSize = this.getPageSize();
		
Iterator<Product> prods =  db.query("SELECT * from PRODUCT", 
	new IteratorPagingResultHandler<Product>(pageNumber,pageSize,Product.class));

// display Product objects…

В приведенном выше примере при значениях pageNumber=2 и pageSize=15 переменная prods содержала бы с 16-й пo 30-ю строки нашей таблицы PRODUCT.

Хранимая процедура CallHandler

Когда объекты базы данных должны возвращать несколько значений, хранимые процедуры ведут себя по-своему. Если операторы SQL возвращают наборы результатов, а UDF возвращают значения (скалярные или табличные), то хранимые процедуры, кроме нескольких наборов результатов, могут возвращать множество значений в параметрах OUT и INOUT. Это свойство делает хранимые процедуры сложным ресурсом СУБД для управления с использованием JDBC . Чтобы обращаться ко всей информации, возвращаемой при обращении к хранимой процедуре, разработчик должен заранее зарегистрировать все выходные параметры и назначить результат обращения к объекту ResultSet. К счастью, pureQuery предоставляет тип объектов StoredProcedureResult, который можно использовать для хранения всей выходной информации, генерируемой при вызове хранимой процедуры, включая выходные параметры и наборы результатов.

Следующий пример кода (листинг 16) показывает, как использовать тип объектов StoredProcedureResult для доступа ко всей выходной информации при обращении к хранимой процедуре.

Листинг 16. Обработка выходных данных хранимой процедуры с использованием StoredProcedureResult
//...
int medianSalary = 75000;

StoredProcedureResult spr = db.call ("call TWO_RESULT_SETS (?)", medianSalary);

String[] outParms = (String[])spr.getOutputParms ();

System.out.println ("Output Parameter(s) length: " + outParms.length);
System.out.println ("List of Products");

Iterator<Product> prods = spr.getIterator(Product.class);
while (prods.hasNext()) {
Product p = prods.next();
System.out.println("Name: " + p.getName());
}

spr.close ();  
// ...

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

Обработка курсора

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

Если вам нужен контроль над тем, как данные извлекаются из базы данных, pureQuery предоставляет возможность определять тип, уровень параллелизма и способность задержки курсора базы данных, используемого для выборки данных. Эти параметры можно передать в качестве параметров методам API или определить как аннотации pureQuery при использовании стиля на основе аннотированных методов. Действительные значения этих параметров отражают значения, поддерживаемые JDBC API (поддерживаемые значения можно проверить на странице API ResultSet JDBC ).

В листинге 17 показан вызов pureQuery API, используемый для задания параметров курсора.

Листинг 17. Задание атрибутов курсора в вызове API pureQuery
//...
Data db = DataFactory.getData(getConnection());
Iterator<Product> prods = db.queryIterator(java.sql.ResultSet.TYPE_FORWARD_ONLY, 
		java.sql.ResultSet.CONCUR_READ_ONLY, 
		java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT, 
		"SELECT * FROM PRODUCT", Product.class);
//...

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

Закрытие ресурсов

Хотя pureQuery решает большую часть задач управления, повышая общую производительность вашего приложения, бывают случаи, когда нужно думать о закрытии ресурсов.

pureQuery помогает автоматически закрывать операторы, наборы результатов и курсоры, когда они больше не используются. Кроме того, он закрывает объекты типа ResultIterator и StoredProcedureResult после употребления их содержания. Если вызов API возвращает один из объектов этих типов, и вы полностью использовали их содержимое, они автоматически закрываются самим pureQuery.

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

В листинге 18 показано, как закрыть объект Iterator, возвращаемый pureQuery.

Листинг 18. Закрытие объекта ResultIterator
//...
Iterator<Product> prods= db.queryIterator("SELECT * from DB2ADMIN.PRODUCT",Product.class);
		
// work with iterator variable "prods"
		
((ResultIterator<Product>)prods).close();

Так как pureQuery возвращает объект родового типа Iterator, нужно передать переменную prods в объект типа ResultIterator<T>, потому что это тип итератора, реализованный pureQuery, и он обеспечит функциональность close().

Заключение

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

Я надеюсь, что вы найдете эту статью полезным ресурсом, который поможет вам в разработке приложений pureQuery. Я жду от вас отзывов по статье и по pureQuery.

Ресурсы

Научиться

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

Обсудить

Комментарии

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=Технология Java, Information Management
ArticleID=494386
ArticleTitle=Создание высокопроизводительных Java-приложений для доступа к данным: Часть 3. Практические приемы работы с API pureQuery в Data Studio
publish-date=06042010