После того как в предыдущей статье («Мир ПК», №12/06, с. 74) мы познакомились с концепцией создания приложений СУБД с использованием WAS CE и с понятием пула соединений, можно перейти к созданию простого приложения, взаимодействующего с БД.
Использование БД в серверных приложениях J2EE
Эта тема очень интересная и непростая, и о ней можно написать большую книгу. В данной статье мы, разумеется, не будем детально рассматривать эти вопросы, тем более что они не имеют отношения к конкретным реализациям J2EE-серверов, в частности к WAS CE. Однако понимание основных аспектов взаимодействия серверов приложений и серверов БД полезно в любом случае. Применительно к WAS CE польза будет состоять в том, что читателю станут гораздо более понятны странный на первый взгляд состав jar-файлов ../repository/ tranql/jars/tranql-1.2.2.jar и наличие нескольких rar-коннекторов в каталоге ../repository/tranql/ rars (и их общее назначение).
Локальные и глобальные транзакции
Само понятие транзакции хорошо знакомо программистам, работающим с БД (зачастую, правда, в весьма упрощенном варианте). А вот деление транзакций на локальные и глобальные появилось в связи с использованием распределенных многозвенных систем.
Локальными транзакциями при взаимодействии с базами данных называются транзакции, управление которыми берет на себя СУБД. С точки зрения программиста, использующего универсальный API для доступа к БД (например, JDBC), локальные транзакции создаются и завершаются в привязке к конкретному соединению с БД. В JDBC явное управление локальными транзакциями выполняется за счет вызова методов объекта типа Connection, таких как setAutoCommit(), commit() или rollback(). Этот режим работы характерен для написания приложений в архитектуре «клиент—сервер». СУБД использует однофазный режим завершения таких транзакций — другими словами, процедура завершения транзакции выполняется за одну команду (commit для подтверждения транзакции). При такой работе нет возможности создать транзакцию, которая «объединяла» бы действия — обычно операторы SQL — с использованием нескольких соединений.
В архитектуре «клиент—сервер» использование однофазных локальных транзакций не вызывает никаких трудностей до тех пор, пока не используются распределенные БД, но последнее встречается довольно редко. Программист в коде клиентского приложения устанавливает соединение с СУБД, явно или неявно начинает локальную транзакцию, выполняет те или иные действия, завершает транзакцию и, если она была последней и все нужные действия уже выполнены, разрывает соединение с СУБД.
Если же используется распределенная БД — другими словами, в контексте одной транзакции должны меняться данные сразу в нескольких БД, — то для решения этой задачи многие производители СУБД обеспечили возможность задания транзакции, выполняемой для нескольких БД этого типа. Такие операции можно условно назвать «локальными транзакциями с двухфазным завершением».
Возрастание требований к технологиям создания сложных проектов, их надежности и масштабируемости, а также возможность создания гетерогенных (разнотипных) распределенных БД привели к качественному изменению ситуации — появились технологии создания распределенных систем, наиболее развитыми и совершенными из которых являются CORBA, J2EE и .NET. Для распределенных систем понятие транзакции было распространено на информационную систему в целом, а не только на СУБД как часть такой системы. Транзакционной стала не только информация в долговременных хранилищах — таковыми стали объекты, из которых строится сама система. Вследствие этого встала задача совместной работы на уровне распределенных объектных транзакций и транзакций на уровне БД — хотя бы потому, что многие объекты хранят свое состояние в БД. Для управления распределенными объектными транзакциями в многозвенных системах существует специальная подсистема, которую обычно называют менеджером или координатором транзакций. В модели Distributed Transaction Process, разработанной консорциумом X/Open (сейчас входит в состав Open Group), появился специальный протокол взаимодействия между координатором транзакций и менеджером ресурсов. Задачей менеджера ресурсов является сохранение состояния транзакционных объектов распределенной системы в БД. Этот протокол получил название XA-протокола.
В принципе его можно трактовать как стандартное средство решения проблемы управления транзакциями с двухфазным завершением, не ограниченное возможностями той или иной конкретной реализации СУБД и позволяющее включать в одну распределенную транзакцию разнотипные СУБД, причем стандартным образом.
Поскольку протокол XA по определению является протоколом взаимодействия системных приложений — менеджера транзакций и менеджера ресурсов, то прикладному программисту нет необходимости знать детали этого протокола. Протокол XA — «внутренняя кухня» технологий создания распределенных систем, в том числе J2EE и JDBC.
Итак, глобальными называются транзакции, которые управляются внешним по отношению к СУБД координатором транзакций. Глобальные транзакции могут завершаться как в однофазном, так и в двухфазном режимах.
Транзакции и компонентная модель EJB
Компонентная модель EJB (Enterprise JavaBeans) предназначена для быстрого и качественного создания серверов приложений как основной части распределенных систем. Применительно к теме, затронутой в данной статье, нас будут интересовать два вида EJB-компонентов — session-компоненты с состоянием и entity-компоненты (чтобы окончательно не сбивать читателя с толку с помощью различной их трактовки в разных версиях EJB, будем понимать entity-компоненты в терминах EJB 2.x).
Session-компонент с состоянием — это компонент, который создается на стороне сервера как представитель конкретного клиента, и его задачей является реализация бизнес-логики системы на стороне сервера приложений. Вызов методов этого компонента выполняется (в подавляющем большинстве случаев) в контексте глобальных объектных транзакций. Если состояние этого компонента не нужно сохранять в БД в соответствии с обычной транзакционной логикой, то программисту ничего делать не надо.
Другое дело, если он хочет сохранить состояние создаваемого объекта. Технология EJB не обеспечивает этого автоматически — об этом должен позаботиться сам программист. Обычно для упомянутой цели используется API JDBC, а именно: в коде компонента разработчик создает соединение с БД, начинает локальную транзакцию, задает нужные SQL-операторы, завершает транзакцию (или транзакции) и разрывает соединение.
На самом деле в таком режиме все только выглядит просто. Проблема состоит в том, что разработчик компонента сам не знает, в какой глобальной объектной транзакции участвует данный компонент, и команды управления локальными транзакциями, помещенные в его код, могут противоречить логике работы сервера приложения, который строится из многих составляющих, причем некоторые из них одновременно могут участвовать в одной и той же глобальной транзакции. Чтобы избежать такого рода конфликтов, среда исполнения EJB-компонентов либо запрещает применять команды управления локальными транзакциями в режиме использования транзакций глобальных, либо перехватывает такие команды и игнорирует их, а управление локальными транзакциями на уровне СУБД берет на себя менеджер глобальных транзакций. Другими словами, локальная транзакция на уровне объекта Connection API JDBC начинается и заканчивается не тогда, когда этого требуют правила JDBC или даже сам программист, а тогда, когда соответствующие команды даст глобальный координатор транзакций. Это очень облегчает жизнь прикладному программисту — обычно он просто не помещает в код session-компонентов с состоянием никаких команд управления транзакциями из состава JDBC API. Но за такие удобства для прикладного разработчика должны «расплачиваться» системные программисты — управление соединениями в распределенных системах самым тесным образом связано с управлением глобальными транзакциями.
Еще интереснее обстоит дело с entity-компонентами.
Entity-компоненты по определению являются кэшированным объектным представлением данных из БД на уровне сервера приложения и управляются командами менеджера глобальных транзакций. Несколько упрощая, можно сказать так: при начале глобальной объектной транзакции для данного конкретного компонента его состояние синхронизируется с состоянием БД путем чтения информации из нее (SQL-команда SELECT), при завершении глобальной транзакции измененное в процессе транзакции состояние компонента записывается в БД (SQL-команда UPDATE).
Особенностью entity-компонентов в модели EJB является автоматизация управления состоянием компонента. Программист, создавая компонент, работает с логическими сущностями — как на уровне Java-кода самого компонента, так и на уровне БД. Эта совокупность логических представлений называется «абстрактной схемой» компонента. Для удобства работы с компонентами при таком подходе автору компонента нельзя использовать конкретные названия физических полей в реальной базе данных хотя бы потому, что автор компонента этих названий может просто не знать. То есть использовать при создании компонента язык SQL нельзя. Для тех случаев, когда без него обойтись невозможно, в EJB создан специальный язык, похожий на упрощенный SQL, — Query Language (QL). Операторы QL должны быть переведены в соответствующие SQL-операторы либо на стадии развертывания компонента, либо динамически, в процессе работы. Все это опять-таки увязывает между собой такие разные фрагменты технологий, как управление соединениями, транзакциями, поддержку QL, особенности объектно-реляционного отображения entity-компонентов и многое другое.
После такого общего обзора можно вернуться с небес на землю и создать простое веб-приложение, «общающееся» с БД.
Создание БД и подготовка ее к использованию
В общем случае при использовании WAS CE/Geronimo процесс создания и подготовки БД к работе состоит из следующих шагов:
- запуск (или поиск в сети) нужного сервера БД;
- создание на нем экземпляра БД как логической совокупности таблиц, индексов, представлений, триггеров, хранимых процедур и проч. Для этой цели используются инструменты, поставляемые самим разработчиком СУБД;
- создание пула соединений с этой БД с нужными свойствами и в нужном режиме. Подробно об этом говорилось в предыдущей статье.
В состав WAS CE в качестве одного из сервисов входит СУБД Derby — реализация реляционной БД с открытым исходным текстом. Эта СУБД используется многими сервисами и компонентами WAS CE для решения системных задач, но прикладной программист — особенно для учебных задач — может использовать ее как рабочую СУБД для хранения необходимых данных.
Поскольку СУБД Derby интегрирована в состав WAS CE, использовать ее проще, чем другие СУБД, — в частности, за счет того, что в состав консоли администратора WAS CE входит менеджер управления БД Derby.
В нашем простейшем примере в состав БД (уже существующая БД SystemDatabase) будет входить только одна таблица. Оператор SQL для ее создания имеет такой вид:
CREATE TABLE MYTABLE (
USERNAME VARCHAR (20) NOT NULL ,
PASSWORD VARCHAR (10) NOT NULL ,
FIRSTNAME VARCHAR (20) NOT NULL ,
LASTNAME VARCHAR (20) NOT NULL ,
EMAIL VARCHAR (20) , CONSTRAINT PKC PRIMARY KEY ( USERNAME)
) ;
Выполнить эту команду можно, набрав ее в поле текста команды менеджера Derby (рис. 1) при выбранной нужной БД (SystemDatabase в данном примере).
Рис. 1. Менеджер СУБД Derby |
Нужные данные можно внести в таблицу с помощью одного или нескольких SQL-операторов INSERT. Для просмотра содержимого таблиц Derby можно задать SQL-оператор SELECT или воспользоваться средствами просмотра содержимого, предоставляемыми менеджером БД Derby.
Если пользователь хочет использовать не существующую, а специально созданную БД, то следует задать имя создаваемой БД и нажать кнопку Create. В этом случае нужно будет создавать, разумеется, пул соединений для этой БД.
Что касается предустановленной БД с именем SystemDatabase, то для нее уже создан пул соединений с именем SystemDatasource и списком свойств, отраженным на рис. 2.
Рис. 2. Свойства пула соединений для БД SystemDatabase |
Обратите внимание, что этот пул поддерживает работу в режиме взаимодействия с внешним координатором транзакций и двухфазным завершением.
Использование параметров пула соединений в дескрипторах J2EE-модулей
Об этом подробно говорилось в предыдущей статье, и здесь просто еще раз сформулируем основные положения.
- Важнейшим элементом приложения, использующего БД, является установление связи между пулом и приложением.
- Программист работает с высокоуровневой моделью взаимодействия с СУБД: он получает (обычно с помощью JNDI) доступ к фабрике соединений, роль которой в JDBC играет интерфейс DataSource и производные от него интерфейсы, а затем с помощью вызова метода getConnection() устанавливает логическое соединение с сервером.
- Спецификация J2EE требует, чтобы настройкой занимался не программист, а специалист, реализующий J2EE-роль под названием deployer. Он работает с xml-дескрипторами модулей, из которых строится J2EE-приложение.
- Обычно обращения к пулам соединений с БД выполняются из модулей, которые содержат веб- или EJB-компоненты. Пул соединений трактуется как ресурс J2EE, что приводит к использованию тега в стандартных xml-дескрипторах.
- Специфика реализации практически любого J2EE-сервера требует использования не только стандартного, но и дополнительного xml-дескриптора.
С текстами xml-дескрипторов мы уже знакомы. Основной дескриптор имеет имя web.xml и может содержать следующие строки:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
jdbc/MyDerbySource
javax.sql.DataSource
Container
Shareable
План развертывания WAS CE (специфический для WAS CE дескриптор) имеет имя geronimo-web.xml и может выглядеть так:
xmlns="http://geronimo.apache.org/xml/ns/web"
xmlns:naming="http://geronimo.apache.org/xml/ns/naming"
configId="derby_webdb_app">
/derby_webdb
false
jdbc/MyDerbySource
SystemDatasource
Несмотря на то что в большинстве случаев удобнее всего использовать стандартный подход, при котором файлы дескрипторов имеют предопределенные имена (web.xml и geronimo-web.xml) и находятся в каталоге WEB-INF архива веб-приложения (WAR-файла), возможны альтернативные решения, которые зависят от формата создаваемого приложения.
Простые веб-приложения, подобные рассматриваемому в данном примере, можно создавать, а затем развертывать на сервере в виде отдельного WAR-файла. В этом случае специфический для WAS CE xml-дескриптор можно назвать любым именем и хранить его в произвольном месте, не включая в состав war-архива, а при развертывании приложения на сервере просто указать это имя в командной строке, например:
java -jar " install_dir indeployer.jar" - user system
- password manager /
deploy <имя_файла_архива_приложения>.war <имя_файла_дескриптора>.xml
Использование пула на уровне Java-кода
Для простоты приложение будем создавать в виде JSP-документа (пусть он имеет имя my_derby_app.jsp). б?ольшую часть этого документа будет занимать Java-код, HTML-теги понадобятся только для форматирования выводимых данных. Текст вряд ли нуждается в дополнительных пояснениях:
<%@ page import="javax.sql.*"%> <%@ page import="java.sql.*"%> <%@ page import="javax.naming.*"%> <% Connection con = null; Statement stmt = null; try { Context initContext = new InitialContext(); Context envContext = (Context)initContext.lookup("java:comp/env"); DataSource ds = (DataSource)envContext.lookup("jdbc/MyDerbySource"); con = ds.getConnection(); } catch(java.lang.Exception e) { ... } try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM MYTABLE"); System.out.println("Table MYTABLE contains:"); %> Your user table contains the following entries:
Name | Password | Firstname | Lastname | Email Address |
---|---|---|---|---|
<%=name%> | <%=pw%> | <%=fn%> | <%=ln%> | <%=em%> |
После того как будет создан WAR-файл приложения (который содержит три файла — xml-дескрипторы в подкаталоге WEB-INF и JSP-документ в корневом каталоге), созданное веб-приложение нужно развернуть на сервере с помощью команды deploy.
Для просмотра записей в таблице при работе с браузером нужно задать следующий URL: http://localhost:8080/derby_webdb/my_derby_app.jsp.
* * *
WAS CE предоставляет разработчикам все необходимое для создания приложений БД, доступ к которым осуществляется с помощью использования интернет-браузера на рабочем месте клиента.