Содержание


Генерация Web-приложений на основе J2EE и Ajax при помощи jpa2web

Автоматическое создание Web-интерфейсов пользователя и повторное использование аннотаций JPA

Comments

Что такое jpa2web?

С появлением готовых средств, таких как Hibernate (см. Ресурсы), был сделан громадный шаг в направлении нивелирования противоречий между Java-объектами и их представлением в базе данных. Это особенно касается той легкости, с которой может описываться сохранение объектов путем добавления аннотаций к соответствующим классам. Это освобождает разработчиков от утомительной работы по сохранению и восстановлению объектов из базы данных. Данные проблемы берет на себя Hibernate, однако, разработчикам по-прежнему приходится создавать Web-страницы для отображения объектов. Типичным сценарием создания Web-приложения среднего уровня может быть следующий: сначала разработчик создает набор простых Java-классов (Plain Old Java Objects – POJO) для представления модели предметной области. Затем начинается работа над транзакциями и Web-интерфейсом. При этом часть объектов модели не содержит транзакционных данных. Клиенты, страны, адреса, сотрудники и компании – это типичные примеры объектов модели, которые участвуют лишь в небольшом числе операций.

Почему бы автоматически не генерировать на основе аннотаций слой Web-представления, позволяющий создавать, добавлять, выводить на экран, удалять и искать эти объекты? И почему бы не использовать Ajax при генерации подобного интерфейса? Это и есть главные задачи библиотеки jpa2web, которая работает следующим образом:

  • Входные данные: аннотированные объекты POJO и, возможно, интерфейсные шаблоны.
  • Результат: Web-приложение на основе Ajax для отображения и сохранения объектов модели.
  • Используемые технологии: FreeMarker + ZK + Hibernate (за более подробной информацией об этих технологиях обратитесь по ссылкам в разделе Ресурсы).

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

В следующих разделах мы расскажем, как при работе с jpa2web различные объекты (простые и сложные) могут служить для создания Web-интерфейса на основе Ajax. Затем мы кратко опишем алгоритм работы jpa2web и приведем основные инструкции на тему начала работы с библиотекой. В заключение мы поговорим о применимости jpa2web в различных ситуациях и обсудим пути ее дальнейшего развития.

Простой пример

Модель предметной области, использующаяся в данной статье, может быть знакома некоторым читателям. Она представляет собой адаптированную версию модели из великолепной книги Enterprise JavaBeans, 3.0 Билла Бурка (Bill Burke) и Ричарда Монсона-Хейфеля (Richard Monson-Haefel) (ссылка приведена в разделе Ресурсы). Она содержит класс, моделирующий корабль - Ship.java (см. листинг 1). Он является простейшим примером POJO, так как все его члены имеют примитивные типы данных.

Листинг 1. Ship.java
package com.titan.domain;
import javax.persistence.*;

@Entity
public class Ship implements java.io.Serializable
{
   private int id;
   private String name;
   private double tonnage;

   @Id @GeneratedValue
   public int getId() { return id; }
   public void setId(int id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public double getTonnage() { return tonnage; }
   public void setTonnage(double tonnage) { this.tonnage = tonnage; }
   
   public String toString() { return name;   }
}

Если вы хотите управлять объектами Ship, поддерживая синхронизацию с базой данных, то, скорее всего, вам понадобится два режима интерфейса пользователя: один для добавления и редактирования, а другой для просмотра списка сохраненных кораблей. В первом режиме интерфейс будет представлять собой форму для ввода параметров кораблей, таких как название и водоизмещение. Во втором режиме будет использоваться список элементов с редактируемыми полями (см. рисунок 1):

Рисунок 1. Форма ввода для объектов Ship
Ship Form
Ship Form

На рисунке 1 показана форма, сгенерированная при помощи jpa2web. Как видите, форма содержит три графические кнопки. Первая из них нужна для добавления корабля, вторая – для вывода списка кораблей (см. рисунок 2), а третья – для удаления ранее введенного корабля. Нажатие на кнопку OK приведет к сохранению корабля в базе данных. Обратите внимание на то, что поле ID недоступно для редактирования, потому что данное свойство в классе Ship помечено аннотацией GeneratedValue.

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

Рисунок 2. Вывод списка объектов Ship
Ship Listing
Ship Listing

Классы, содержащие ссылки на другие классы

Ситуация усложняется в случае, если объекты должны содержать ссылки на объекты других классов. Примером подобного может служить класс Cabin, который кроме простых атрибутов связан отношением типа ManyToOne («многие к одному») с классом Ship (см. листинг 2).

Листинг 2. Cabin.java
package com.titan.domain;
import javax.persistence.*;

@Entity
public class Cabin implements java.io.Serializable
{
   private int id;
   private String name;
   private int bedCount;
   private int deckLevel;
   private Ship ship;

   @Id @GeneratedValue
   public int getId() { return id; }
   public void setId(int id) { this.id = id; }
 
   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public int getBedCount() { return bedCount; }
   public void setBedCount(int count) { this.bedCount = count; }

   public int getDeckLevel() { return deckLevel; }
   public void setDeckLevel(int level) { this.deckLevel = level; }

   @ManyToOne
   public Ship getShip() { return ship; }
   public void setShip(Ship ship) { this.ship = ship; }
   
   public String toString() {
                  return name;
   }
}

В этом случае jpa2web генерирует форму, похожую на ту, что использовалась для класса Ship, но с возможностью указания корабля для данной каюты. Данная возможность реализуется с помощью кнопки, открывающей модальное диалоговое окно, которое позволяет выбрать нужный корабль. Сгенерированная форма показана на рисунке 3, а рисунок 4 демонстрирует диалог для выбора корабля. ZK облегчает работу с модальными диалогами, что является еще одним преимуществом данной технологии.

Рисунок 3. Форма ввода для класса Cabin
Cabin Form
Cabin Form
Рисунок 4. Диалоговое окно для выбора корабля
Choosing the ship
Choosing the ship

Классы с несколькими отношениями множественности

Теперь рассмотрим более сложный пример. Что происходит в случае, если класс участвует в отношениях разной множественности. Например, класс Customer (см. листинг 3) связан отношениями OnToOne («один к одному»), ManyToMany («многие ко многим») и OneToMany («один ко многим») с классами CreditCard, Phone и Reservation соответственно.

Листинг 3. Customer.java
package com.titan.domain;
import javax.persistence.*;
@Entity
public class Customer implements java.io.Serializable
{
   private int id;
   private String firstName;
   private String lastName;
   private boolean hasGoodCredit;

   private Address address;
   private Collection<Phone> phoneNumbers = new ArrayList<Phone>();
   private CreditCard creditCard;
   private Collection<Reservation> reservations = 
     new ArrayList<Reservation>();

   @Id @GeneratedValue
   public int getId() { return id; }
   public void setId(int id) { this.id = id; }

   public String getFirstName() { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

   public String getLastName() { return lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }

   public boolean getHasGoodCredit() { return hasGoodCredit; }
   public void setHasGoodCredit(boolean flag) { hasGoodCredit = flag; }

   @OneToOne(cascade={CascadeType.ALL})
   public Address getAddress() { return address; }
   public void setAddress(Address address) { this.address = address; }

   @OneToOne(cascade={CascadeType.ALL})
   @IndexColumn(name="INDEX_COL_CC")
   public CreditCard getCreditCard() { return creditCard; }
   public void setCreditCard(CreditCard card) { creditCard = card; }

   @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.EAGER)
   @IndexColumn(name="INDEX_COL_PHON")
   public Collection<Phone>getPhoneNumbers() { return phoneNumbers; }
   public void setPhoneNumbers(Collection<Phone> phones) { 
       this.phoneNumbers = phones; 
   }

   @ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
   @IndexColumn(name="INDEX_COL_CUS")
   public Collection<Reservation> getReservations() { return reservations; }
   public void setReservations(Collection<Reservation> reservations) { 
       this.reservations = reservations; 
   }
   
   public String toString() {
                  return getLastName()+","+getFirstName();
   }
}

На рисунке 5 показана форма, соответствующая классу из листинга 3. Страница содержит несколько секций, которые мы рассмотрим последовательно.

Рисунок 5. Форма ввода для объектов Customer
Customer Form
Customer Form

Сначала рассмотрим часть формы, отвечающую за связь с классом CreditCard.

Отношение Customer-CreditCard типа OneToOne

Данная ситуация напоминает рассмотренное выше отношение типа ManyToOne между классами Cabin и Ship, для работы с которым использовался модальный диалог. Однако теперь типом отношения является OneToMany, и каждая кредитная карта неразрывно связана со своим владельцем, поэтому диалог для выбора существующего объекта тут не подходит. Вместо этого используется форма, позволяющая редактировать данные новой кредитной карты для текущего клиента. Диалоговое окно с данной формой показано на рисунке 6).

Рисунок 6. Ввод информации о кредитной карте
Completing the Credit Card details
Completing the Credit Card details

Отношение Customer-Phone типа OneToMany

Для работы с отношением Customer-Phone генерируется таблица, строками в которой являются телефонные номера. Информацию о каждом номере можно редактировать непосредственно в таблице. Новые номера могут быть добавлены в таблицу при помощи кнопки Add Row.

Отношение Customer-Reservation типа ManyToMany

При помощи кнопки Add.. можно добавлять новые заказы в список, а также выбирать ранее созданные заказы в модальном диалоговом окне. На рисунке 7 показана заполненная форма, содержащая несколько телефонных номеров и заказов.

Рисунок 7. Воод информации о телефонном номере клиента
Completing the customer phone details
Completing the customer phone details

Оформление окон

На данный момент внешний вид сгенерированных окон оставляет желать лучшего. Существуют две главные проблемы: во-первых, текстовые метки полей формы совпадают с именами переменных-членов Java-классов, а во-вторых, отсутствует продуманный порядок следования полей. Подобные недостатки можно устранить при помощи специальных аннотаций jpa2web. Например, внешний вид формы для класса Cabin (см. рисунок 8) можно улучшить, добавив аннотации MemberField , как показано в листинге 4. Сравните данное оформление с первоначальной версией окна, показанной на рисунке 3. Теперь поля формы имеют осмысленные названия и расположены в значительно более логичном порядке.

Рисунок 8. Оформление формы ввода информации о каюте
Decorated Cabin Form
Decorated Cabin Form
Листинг 4. Добавление аннотаций к классу Cabin для оформления интерфейса
@Entity
public class Cabin implements java.io.Serializable
{
   @Id @GeneratedValue @MemberField(order=1,showName="ID")
   public int getId() { return id; }
 
   public String getName() { return name; } @MemberField(order=2,showName="Cabin Name")

   @MemberField(order=2,showName="Number of Beds")
   public int getBedCount() { return bedCount; }

   @MemberField(order=3,showName="Deck Level")
   public int getDeckLevel() { return deckLevel; }

   @ManyToOne @MemberField(showName="Ship")
   public Ship getShip() { return ship; }
   
....
}

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

Запуск приложения, созданного при помощи jpa2web

Теперь, разобравшись с особенностями приложения, созданного при помощи jpa2web, пора посмотреть на него в действии. Выполните следуюшие шаги, необходимые для запуска примера под сервером Apache Tomcat:

  1. Загрузите jpa2web с Sourceforge.
  2. Распакуйте дистрибутив библиотеки в каталог на вашем диске. Далее этот каталог мы будет обозначать как [dir].
  3. Загрузите Tomcat с сайта Apache и установите его в другой каталог на диске. Данный каталог мы будем именовать [tomcatdir].
  4. Скопируйте все JAR-файлы из папок [dir]/zklibs и [dir]/jboss_libs в [tomcatdir]/lib.
  5. Выберите нужный драйвер JDBC, загрузите его и скопируйте файл .jar в папку [tomcatdir]/lib.
  6. Поместите исходный код классов модели (классы JavaBean с аннотациями JPA) в папку [dir]/modelsrc.
  7. Отредактируйте файл [dir]/templates/hibernate-cfg.xml так, чтобы он содержал корректные параметры соединения с базой данных. Пример подобного файла приведен ниже.
<hibernate-configuration>
<session-factory name="thefactory">
	<property name="hibernate.connection.driver_class">
		net.sourceforge.jtds.jdbc.Driver
	</property>
	<property name="hibernate.dialect">
		org.hibernate.dialect.SQLServerDialect
	</property>
	<property name="hibernate.hbm2ddl.auto">update</property>
	<property name="hibernate.connection.url">
		jdbc:jtds:sqlserver://127.0.0.1:1433/test</property>
	<property name="hibernate.connection.username">sa</property>
	<property name="hibernate.connection.password">*</property>
	<property name="hibernate.max_fetch_depth">0</property>
	<#list windows as win><mapping class="${win.mainClass.name}" />
	</#list>
</session-factory>
</hibernate-configuration>
  1. Отредактируйте файл [rootdir]/jpa2web-loader.xml так, чтобы он содержал названия пакетов тех классов модели, для которых должен генерироваться интерфейс.
  2. Скорректируйте пути к каталогу Tomcat в ANT-файле build-tomcat.xml и выполните задание по умолчанию: $ ant -buildfile build-tomcat.xml .
  3. Теперь все готово. В каталоге [dir]/dist должна появиться папка WAR, содержащая скомпилированное Web-приложение. Она также должна была быть скопирована в каталог webapps в директории Tomcat. Таким образом, приложение должно быть доступно по URL наподобие http://localhost:8080/sampleapp.war.

Генератор jpa2web

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

Библиотека принимает на вход набор классов с аннотациями JPA. Далее создается специальное внутреннее представление данных классов JavaBean и отношений между ними, называемое загрузчиком. На данный момент в jpa2web реализован только EntityLoader (загрузчик, анализирующий аннотации JPA), но могут быть созданы и другие, например, загрузчик, работающий с конфигурационными файлами XML, в которых может содержаться информация для генерации интерфейса. Далее это внутреннее представление поступает на вход генератора, который также принимает набор шаблонов FreeMarker (более подробная информация о FreeMarker приведена в разделе Ресурсы). Использование шаблонов придает jpa2web особую гибкость при генерировании окон. Все фазы процесса генерации показаны на рисунке 9. Результатом работы генератора является каталог WAR, в котором содержится готовое к работе Web-приложение, включающее в себя окно для каждого аннотированного класса JavaBean, а также главную страницу для перехода к нужному окну.

Рисунок 9. Схема процесса генерации приложения
Generation Process
Generation Process

В упрощенном виде псевдокод алгоритма генератора можно представить следующим образом:

Алгоритм генерации
Для каждого объекта модели:
  - создать окно формы с названием полное.имя.класса.zul:
    для каждого простого поля (field):
      if (field is String) создать элемент типа textbox
      if (field is int/byte/short) создать элемент типа integerbox
      if (field is boolean) создать элемент типа checkbox
      if (field is double/float) создать элемент типа doublebox/floatbox
    для каждого класса, связанного отношением типа OneToOne:
      создать кнопку для открытия формы, позволяющей создавать 
      экземпляр связанного класса
    для каждого класса, связанного отношением типа ManyToOne:
      создать кнопку, открывающую окно списка для выбора нужного 
      экземпляра связанного класса 
    для каждого класса, связанного отношением типа OneToMany:
      создать таблицу с экземплярами связанного класса, а также кнопки
      для добавления и удаления строк
    для каждого класса, связанного отношением типа ManyToMany:
      создать элемент типа listbox, содержащий связанные объекты,
      а также кнопку, открывающую окно списка для выбора экземпляра связанного класса
  - создать окно с названием list.полное.имя.класса.zul, в котором будут
    перечислены сохраненные экземпляры данного класса  
  - добавить аннотированный класс в секцию файла hibernate.cfg.xml,
    описывающую отображения 
  - добавить ссылки в панель меню в обоих окнах, сгенерированных для данного класса

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

Заключение

Существует несколько библиотек, позволяющих генерировать исходный код Web-приложений на основе некоторого набора входных данных. Многие из них представляют собой полноценные реализации MDA – модельно-ориентированной архитектуры (model-driven architecture). В отличие от них jpa2web является легковесной библиотекой, предоставляющей средства для генерации кода на основе аннотаций.

Разработчикам придется столкнуться с определенными трудностями при создании генератора, который можно было бы использовать в крупных приложениях. Модели, в которых отношения между классами представляют собой сложные направленные графы, не будут поддерживаться jpa2web, особенно при наличии сложных циклических ссылок. На момент написания данной статьи jpa2web не поддерживала отношения типа OneToMany и ManyToMany для класса, связанного с данным отношением OneToMany (хотя эта возможность практически осуществима и может быть добавлена в план развития библиотеки). Тем не менее, имеет смысл продолжать исследовать вопрос генерации оконного интерфейса для сложных моделей. Вероятно наиболее ярким примером ситуации, при которой jpa2web будет бесполезна – это создание Web-приложений, активно использующих транзакции, а также приложений, в которых нет прямого соответствия между объектной моделью и интерфейсом.

Несмотря на свои ограничения, jpa2web может быть полезной в ряде случаев. Ее можно использовать для быстрого создания Web-интерфейса для аннотированных объектов модели, не участвующих в транзакциях. Вы также можете применять эту библиотеку в процессе тестирования, создавая необходимые объекты в базе данных, не прибегая при этом к созданию громоздких скриптов для генерации тестовых данных. В целом, jpa2web следует рассматривать как средство быстрого создания прототипов, а не законченных продуктов. Сгенерированный с ее помощью код отличается относительной простотой, поэтому в него несложно добавить дополнительные проверки и обработку данных. При использовании jpa2web в крупных приложениях могут возникать дополнительные проблемы, связанные с транзакциями, обеспечением безопасности и ограничениями многоуровневой архитектуры, но, приложив определенные усилия, их можно преодолеть.

В будущих версиях библиотеки планируется расширить поддержку сложных типов отношений между классами модели, добавить дополнительные аннотации для более гибкого управления генерируемым интерфейсом, а также реализовать возможность генерации кода, использующего другие инструментарии Ajax, такие как Google GWT, OpenLaszo и Echo2. Также могут быть добавлены новые варианты входных данных для генератора, например, в виде XML-файлов, описывающих модель приложения. Если вы бы хотели узнать больше о возможностях jpa2web или принять участие в работе над библиотекой, то обратитесь к автору данной статьи.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, SOA и web-сервисы
ArticleID=449824
ArticleTitle=Генерация Web-приложений на основе J2EE и Ajax при помощи jpa2web
publish-date=11262009