Продолжается подписка на наши издания! Вы не забыли подписаться?

JPA в среде Java EE

Автор: Александр Цимбал
Опубликовано: 08.07.2010
Версия текста: 1.1

Персистентный модуль JPA (Persistence Unit) в JavaEE
Entity-менеджеры и контексты персистентности JPA в JavaEE
Роль session-компонентов при работе с JPA и взаимодействие с entity-классами
Stateless-компоненты
Stateful-компоненты
Контексты персистентности, управляемые приложением
Entity-листенеры
Задание параметров JPA в JavaEE-приложении
Создание JavaEE-приложения с использованием JPA для Eclipse и Geronimo
Создание и регистрация источника данных (JDBC-интерфейса DataSource)
Параметры проекта Eclipse для использования Geronimo, EJB и JPA
Заключение

Несмотря на то, что JPA можно с успехом использовать в среде JavaSE, основная область применения этой технологии – JavaEE приложения. Данная статья посвящена особенностям применения JPA в среде JavaEE и является продолжением статьи «JPA в среде JavaSE».

Персистентный модуль JPA (Persistence Unit) в JavaEE

Работа c JPA в среде JavaEE происходит (как правило) в следующих режимах:

Этот режим принципиально отличается от работы в среде JavaSE. Приложение не создает явно ни фабрику менеджеров персистентности, ни самих менеджеров – это делает контейнер при создании экземпляров session-компонентов, управляющих ходом выполнения приложения (обычно с использованием dependency injection). Кроме того, каждый такой менеджер персистентности работает не с собственным контекстом, а с «общим» на время выполнения транзакции, который автоматически передается между компонентами, участвующими в транзакции, вместе с контекстами транзакции и безопасности.

Единственный серьезный выбор, который должен сделать разработчик – выбор между TRANSACTION-контекстами, которые создаются заново для каждой глобальной транзакции и закрываются после ее завершения, и EXTENDED-контекстами, которые могут принимать участие в нескольких последовательных транзакциях, пока не будут закрыты разработчиком явно. Но даже и в этом случае выбор ограничен – использование session stateless-компонентов естественно подразумевает использование TRANSACTION-контекстов, session stateful-компонентов – EXTENDED-контекстов. Другой подход сопряжен с созданием искусственных и сложных программных конструкций. Принятая архитектура приложения достаточно жестко диктует решения на низком уровне.

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

Необходимо также иметь в виду вопросы многопоточности. Компонентная модель EJB, в общем и целом, запрещает (или не рекомендует) разработчикам создавать свои потоки или закрывать уже существующие – этим должен заниматься исключительно контейнер. Архитектура JPA предполагает, что работа с каждым конкретным контекстом персистентности выполняется в однопоточном режиме (это не относится к фабрике менеджеров контекстов – методы интерфейса EntityManagerFactory могут вызываться в многопоточном режиме и должны быть реализованы как потокобезопасные). Такой подход вполне гармонирует с моделью многопоточности в EJB – вызов session-компонента в контексте двух различных транзакций приводит к системному исключению, а наличие двух различных транзакций практически всегда означает наличие двух различных потоков.

Итак, модуль персистентности в JavaEE.

Как и в случае JavaSE, приложение может (при необходимости) иметь несколько модулей персистентности для одной JVM, т.е. создать несколько фабрик контекстов персистентности, но такой подход нехарактерен для реальных JavaEE-приложений. Создавать несколько модулей персистентности имеет смысл тогда, когда нужно использовать различные хранилища данных. Хотя работа с различными БД не является исключением в рамках распределенной системы в целом (которая может быть построена из нескольких приложений, выполняемых под управлением различных серверов), но применительно к JPA нужно ограничиться отдельным приложением. Просто потому, что контекст персистентности (в отличие от контекстов транзакции и безопасности) не передается при выполнении удаленных вызовов – это исключительно локальная сущность. Таким образом, при обсуждении JPA мы ограничиваем себя отдельным JavaEE-приложением. В этом случае использование нескольких различных хранилищ и, как следствие, транзакций с двухфазным завершением, как правило, только неоправданно усложняет программу.

Но даже и создание единственного персистентного модуля не требует создания фабрики менеджеров персистентности – гораздо удобнее возложить эту обязанность на контейнер, оставив программисту только работу по созданию менеджеров персистентности, обычно в самом удобном и простом варианте – с использованием dependency injection. Это не значит, что разработчик не обязан создавать сам модуль (или модули) персистентности.

Тем не менее, рассмотрим создание фабрики менеджеров контекстов.

Прежде всего, необходимо создать модуль персистентности. Для этого в приложение необходимо добавить файл META-INF/persistence.xml, в котором необходимо задать имя модуля (или модулей) персистентности, а также указать класс провайдера персистентности (если он не задан тем или иным образом по умолчанию). Остальная информация не является обязательной, если выбранный провайдер использует по умолчанию некоторую БД с «известным» ему JNDI-именем и пр.

Поскольку в примерах для данной статьи используется JavaEE-сервер Geronimo, то файл persistence.xml в «минимальной конфигурации» может иметь следующий вид:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  
  <persistence-unit name="my_jta_unit" transaction-type="JTA">
    <provider>
      org.apache.openjpa.persistence.PersistenceProviderImpl
    </provider>
  </persistence-unit>
</persistence>

Реализация EJB-контейнера в Geronimo (OpenEJB) использует в качестве класса JPA-провайдера класс org.apache.openjpa.persistence.PersistenceProviderImpl. Задавать в дополнительном XML-дескрипторе зависимость (тег <dependency>) создаваемого приложения от CAR-файла Geronimo для OpenEJB не нужно.

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

import javax.ejb.Stateless;
import javax.persistence.*;

@Stateless
@Remote (MyRemoteBusinessInterface.class)
public class MyStatelessBeanWContext implements MyRemoteBusinessInterface 
{
  @PersistenceUnit
  private EntityManagerFactory emf;
  
  public MyStatelessBeanWContext() { }
  ...
}

Аннотация PersistenceUnit имеет следующий синтаксис:

@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface PersistenceUnit
{
  String name() default "";
  String unitName() default "";
}

Параметр name задает JNDI-имя фабрики менеджеров (относительно локального контекста «java:comp/env»), если аннотация задана для класса компонента без использования dependency injection (что используется достаточно редко), например:

@Stateless
@Remote (MyRemoteBusinessInterface.class)
@PersistenceUnit (name="PersistenceUnitJNDIName")
public class MyStatelessBeanWContext implements MyRemoteBusinessInterface 
{
  
  private EntityManagerFactory emf;
  
  public MyStatelessBeanWContext() 
  {
    Context context = null;
    try 
    {
      context = new InitialContext();
      emf = (EntityManagerFactory)
      context.lookup("java:comp/env/PersistenceUnitJNDIName");
    } 
    catch (NamingException e) { }
  }
  ...
}

Необязательный параметр аннотации unitName задает имя нужного модуля персистентности – так, как это имя задано в файле persistence.xml. Этот параметр используется в случае, когда для приложения определено несколько модулей персистентности. Параметры нескольких модулей могут быть сообщены классу приложения с помощью аннотации @PersistenceUnits:

@Target(TYPE) @Retention(RUNTIME)
public @interface PersistenceUnits
{
  PersistenceUnit[] value();
}

Entity-менеджеры и контексты персистентности JPA в JavaEE

При работе в JavaSE-среде создание нового Entity-менеджера приводит к созданию нового (пустого) контекста персистентности, т.е. каждый менеджер персистентности работает со своим собственным контекстом. Такой режим работы очень неудобен при использовании стандартных компонентов JavaEE, поэтому в среде JavaEE предусмотрен способ, который позволяет нескольким менеджерам работать с одним и тем же контекстом. Этот способ – управление менеджерами персистентности и контекстами с помощью EJB-контейнера и JTA-координатора транзакций.

Для создания менеджеров, управляемых контейнером, программист не должен явно вызывать метод createEntityManager() интерфейса EntityManagerFactory. Вместо этого необходимо использовать механизм dependency injection или JNDI. В обоих случаях нет никакой необходимости использования фабрики менеджеров.

Хотя можно задать параметры менеджеров персистентности на уровне XML-дескрипторов, гораздо более удобно и естественно применить аннотацию @PersistenceContext, которая имеет следующий вид:

public enum PersistenceContextType 
{
  TRANSACTION,
  EXTENDED
}

@Target(value={})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface PersistenceProperty 
{
  public String name();
  public String value();
}

@Target(value={ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface PersistenceContext 
{
  public String name() default "";
  public String unitName() default "";
  public PersistenceContextType type() 
    default PersistenceContextType.TRANSACTION;
  public PersistenceProperty[] properties() default {};

}

Параметры name и unitName аннотации PersistenceContext – те же, что и в ранее рассмотренной аннотации PersistenceUnit. Обычно они не используются, так как в большинстве случаев для приложения определен только один модуль персистентности, а вместо JNDI удобнее использовать dependency injection.

Параметр properties позволяет задать для менеджера персистентности набор свойств, зависящих от реализации. Эти свойства можно задать также с помощью тегов <property> в файле persistence.xml, как уже было показано в статье, посвященной JPA в среде JavaSE.

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

Примеры создания управляемых контейнером менеджеров контекстов в режиме TRANSACTION (значение по умолчанию) с использованием dependency injection:

@Stateless
@Remote (MyRemoteBusinessInterface.class)
public class MyStatelessBeanWContext implements MyRemoteBusinessInterface 
{
  @PersistenceContext private EntityManager em;
  ...
}

...и JNDI:

@Stateless
@Remote (MyRemoteBusinessInterface.class)
@PersistenceContext (name="PersistenceContextJNDIName")
public class MyStatelessBeanWContext implements MyRemoteBusinessInterface 
{
  
  private EntityManager em;

  public MyStatelessBeanWContext() 
  {
    Context context = null;
    try 
    {
      context = new InitialContext();
      em = (EntityManager)
      context.lookup("java:comp/env/PersistenceContextJNDIName");
    } 
    catch (NamingException e) 
    {
    }
  }
  ...
}

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

Роль session-компонентов при работе с JPA и взаимодействие с entity-классами

Начинающие EJB-программисты часто недопонимают смысл использования JPA вместе с session-компонентами – вплоть до вопроса, может ли быть session stateful-компонент одновременно и Entity-сущностью JPA – для того, чтобы сохранять в БД состояние самого компонента (вопрос, может ли быть EJB-компонент одновременно и session-, и entity-компонентом, в EJB 2.x по понятным причинам не возникал).

Entity-сущности JPA, как и Entity-компоненты EJB 2.x, выполняют (по замыслу разработчиков спецификации) в приложении роль кэша информации из БД на уровне серверов приложений. Это просто объектное представление записей из таблиц реляционных БД. Если для entity-компонентов это было практически очевидно (компонент определен строго формально, и его использование связано с большим числом ограничений), то entity-сущность JPA – потенциально гораздо более гибкая и даже универсальная программная конструкция. Тем не менее, роль ее остается тот же самой, что и у entity-компонента, хотя, конечно, entity-сущности можно использовать самыми различными способами, поскольку это все-таки обычный класс Java.

Экземпляр entity-класса ничем не отличается от других объектов Java – до тех пор, пока он явно не включен в контекст персистентности. Управляют контекстами персистентности в JavaEE session-компоненты. Они отвечают за создание и уничтожение контекстов, за добавление entity-сущностей в контексты и исключение их из контекстов, они (вместе со своими контейнерами) отвечают за начало и завершение объектных транзакций.

Если session-компонент является компонентом с состоянием, то его состояние можно, разумеется, записать в БД, используя любой из возможных подходов – от обращения к API конкретного СУБД и использования JDBC до создания отдельного программно-управляемого контекста JPA. Но состояние session-компонента – это, по определению, информация с небольшим сроком жизни, а именно, на время сеанса работы данного пользователя с данным компонентом. В большинстве случаев эта информация после завершения сеанса работы уже не нужна (хотя она может пригодиться, например, для фиксации действий, выполненных данным пользователем, с целью контроля совершаемых действий).

Состояние session-компонентов – это одно, а поля entity-сущностей – совсем другое. Не следует путать эти вещи – следствием станут непонятные и неудобные для сопровождения и модификации программные конструкции.

Поскольку время существования entity-сущностей как Java-объектов только косвенно связано со временем, когда эти объекты включены в контексты персистентности, нужно отчетливо понимать следующее. Entity-сущности – это кэшированные данные из БД, введенные для повышения удобства работы с такими данными и (потенциально) для большей эффективности. Но, как и в случае любого другого кэша, состояние entity-сущности «устаревает» сразу после выполнения реляционно-объектного отображения, т.е. чтения данных из хранилища в память. Это означает, что и время существования объекта (т.е. время после выполнения операции new и до пометки объекта, как подлежащего уничтожению «сборщиком мусора» Java), и время нахождения этого объекта в контексте персистентности (состоянии managed) должны быть не более действительно необходимого.

В наибольшей степени этим требованиям отвечает использование session-компонентов без состояния, которые создаются (вообще говоря) для выполнения одного бизнес-метода в контексте одной транзакции. При этом удобно использовать контексты персистентности типа PersistenceContextType.TRANSACTION. Такие контексты создаются при вызове бизнес-метода (в нормальном режиме работы – в контексте глобальной транзакции), создают сопоставленный с ними контекст персистентности, entity-сущности в который добавляются только на время выполнения этой транзакции, и уничтожаются после завершения транзакции/завершении работы метода вместе со своим контекстом персистентности. Это наиболее безопасный и эффективный режим использования JPA в JavaEE-приложениях.

Поскольку время существования экземпляра session-компонента без состояния определяется контейнером, а не программистом, спецификация EJB вообще запрещает создание управляемых контейнером EXTENDED-контекстов персистентности для stateless-компонентов.

Использовать управляемые контейнерами EXTENDED-контексты, которые существуют в процессе выполнения нескольких объектных транзакций следует только в случае реальной необходимости.

Stateless-компоненты

Под экземпляром session-компонента (как с состоянием, так и без) и в EJB 2.x, и в EJB 3 понимается экземпляр класса компонента. Этот экземпляр в случае stateless-компонента создается и уничтожается только контейнером – разработчик и администратор могут влиять на этот процесс только косвенно, задавая параметры контейнера, например, размер пулов или величины интервалов тайм-аутов. При вызове create-метода home-интерфейса в EJB 2.x или при получении объектной ссылки на бизнес-интерфейс в EJB 3 программист получает ссылку на интерфейсный промежуточный объект, управляемый контейнером. Конкретный экземпляр компонента создается (или берется из пула готовых экземпляров) в момент вызова клиентом одного из бизнес-методов. Кроме того, важно понимать, что удаление экземпляра (или возврат его в пул экземпляров) контейнер может выполнять сразу после завершения вызванного метода. Не имеет смысла «связывать» конкретный экземпляр компонента без состояния с текущим сеансом работы, так как все экземпляры этого класса в одинаковой степени подходят для обслуживания любого клиентского обращения.

Но в случае с использованием JPA возникает дополнительная задача – управление циклом жизни контекста персистентности (здесь и далее будем говорить только о контекстах, управляемых контейнером, а не приложением).

При написании кода класса компонента программист не может знать, как именно будет использоваться этот компонент в разных приложениях, кто обращается к нему в каждом конкретном случае, существуют ли в момент обращения (и передаются ли вместе с параметрами вызова) контексты глобальной транзакции и/или контекста персистентности. Правда, программист всегда задает (явно или неявно) режим управления транзакциями (Required, Requires New и Mandatory в случае поддержки глобальных транзакций).

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

Если при вызове бизнес-метода транзакция уже существует (в том числе начатая самим компонентом непосредственно перед выполнением кода вызванного бизнес-метода) то возможны следующие два варианта с точки зрения управления циклом жизни контекстами персистентности JPA:

Сложнее ситуация, если глобальной транзакции не существует (ее контекст не передается вызываемому бизнес-методу). В этом случае не передается и контекст персистентности. Stateless-компоненты ведут себя следующим образом: создается менеджер персистентности, и при обращении к его методам создается новый пустой контекст.

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

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

Такой режим работы не вызывает никаких трудностей. Транзакция начинается – где-то по цепочке вызовов stateless-компонентов происходит обращение к менеджеру персистентности – создается контекст (в режиме TRANSACTION) – он передается дальше по цепочке вызовов вместе с контекстом транзакции - участвующие session-компоненты добавляют в него entity-сущности (и/или удаляют их) – транзакция завершается при завершении породившего ее метода – если commit(), то данные managed-сущностей записываются в БД – все сущности выводятся из контекста (переводятся в состояние detached) – TRANSACTION-контекст уничтожается.

Stateful-компоненты

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

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

Если stateful-компонент использует dependency injection (или метод lookup() JNDI) для получения доступа к управляемому контейнером менеджеру персистентности, то важно, какой тип имеет данный контекст – TRANSACTION или EXTENDED.

Если контекст с помощью аннотации @Per­sis­ten­ce­Context или соответствующего тега XML-дескриптора (<persistence-context-ref>) объявлен как TRANSACTION-контекст, то все происходит так, как было описано выше для stateless-компонентов.

Если контекст персистентности объявлен как EXTENDED-контекст, то он создается в момент создания экземпляра такого stateful-компонента, а затем «привязывается» (bound) к этому компоненту. Это принципиальное отличие от stateless-компонентов. Привязанный контекст становится, с одной стороны, «контекстом по умолчанию», если нет другого контекста – например, переданного по цепочек вызовов. С другой стороны, он становится «единственно-возможным» контекстом – если по цепочке вызовов метода данного stateful-компонента передан (вместе с контекстом JTA-транзакции) другой контекст персистентности, то контейнер возбуждает исключение EJBException.

Завершение текущей транзакции для EXTENDED-контекста выполняется по-разному в зависимости от способа завершения – подтверждения транзакции или ее отката. В случае ее отката все объекты контекста переводятся в состояние detached (возможно, с некоторыми особенностями). Если же транзакция подтверждается, то managed-объекты остаются в состоянии managed. Важно также понимать, что начало новой транзакции не приводит к автоматической синхронизации объектов в памяти с состоянием БД. Объекты в памяти будет иметь то же состояние, которое они имели при завершении предыдущей транзакции.

Контекст персистентности, привязанный к данному stateful-компоненту, уничтожается вместе с экземпляром компонента, т.е. либо в результате вызова @Remove-метода, либо по истечению интервала тайм-аута.

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

И еще один важнейший момент, о котором часто забывают. Бизнес-методы stateful-компонентов не должны возвращать указатели на персистентные объекты, например, так:

@Stateful
@Remote (MyBusinessInterface.class)
public MyStatefulBean implements MyBusinessInterface {
  @PersistenceContext(type= PersistenceContextType.EXTENDED))
  private EntityManager em;
  private MyType persistence_field;  // ссылка на персистентный объект

  public MyStatefulBean ()
  {
    persistence_field = new MyType();
    em.persist (persistence_field);
    ...
  }

//  Метод удаленного бизнес-интерфейса
  public MyType myBusinessMethod ()
  {
    ...
    return persistence_field;
  }

// Локальный метод, не связанный с бизнес-интерфейсом
  public getPersistence_field()
  {
    return persistence_field;
  }
  ...
}

Возврат результата в методах myBusinessMethod() и getPersistence_field() приводит к совершенно различным результатам. При вызове getPersistence_field() не происходит ничего с точки зрения статуса персистентного объекта – если он находился в состоянии managed, то и останется в том же состоянии. Если же пользователь вызовет метод myBusinessMethod(), то, поскольку этот метод удаленный, для возврата объекта по сети он будет подвергнут сериализации, что в JPA приводит к переводу managed-объекта в состояние detached.

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

@Stateful
@Remote (MyBusinessInterface.class)
public MyStatefulBean implements MyBusinessInterface 
{
  @PersistenceContext(type= PersistenceContextType.EXTENDED))
  private EntityManager em;
  private MyType persistence_field;  // ссылка на персистентный объект

//  Метод удаленного бизнес-интерфейса
  public void refreshState ()
  {
    if (persistence_field == null)
      throw new IllegalStateException(); // как вариант
    if (!em.contains(persistence_field))
    {
      persistence_field = em.merge();
      em.refresh(persistence_field);
    }
  }
  ...
}

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

Еще одна интересная составляющая использования EXTENDED-контекстов в stateful-компонентах – случай, когда один экземпляр stateful-компонента с привязанным EXTENDED-контекстом создает другой – неважно, с помощью dependency injection или JNDI.

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

Чтобы этого избежать, в JPA при таком ходе событий для второго компонента не создается свой EXTENDED-контекст. Вместо этого он наследует контекст первого – независимо от наличия или отсутствия текущей транзакции в момент создания второго компонента. Унаследованный контекст привязывается ко второму компоненту. Получается, что контекст первого компонента привязан к обоим компонентам (или к нескольким, если второй компонент создает третий, и т.д.). Если EXTENDED-контекст привязан к нескольким stateful-компонентам, то он не будет уничтожен до тех пор, пока не будут уничтожены все экземпляры, к которым он привязан.

Контексты персистентности, управляемые приложением

В JavaEE-приложениях пользователь может использовать и контексты, управляемые этим приложением, а не контейнером. Как получить в JavaEE фабрику менеджеров персистентности, было показано выше. Как создавать, использовать и закрывать такие контексты, описано в предыдущей статье. Создаются эти контексты явно, при вызове метода createEntityManager() интерфейса EntityManagerFactory, а уничтожаются – при вызове метода close() интерфейса EntityManager.

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

Определить, вызывается ли метод session-компонента в контексте глобальной транзакции, сейчас непросто. В EJB 2.1 не было никаких проблем – разработчик получал с помощью JNDI ссылку на интерфейс UserTransaction, а затем вызывал метод, возвращающий статус транзакции.

В EJB 3.0 получить доступ к UserTransaction можно только для компонентов в режиме BMT (Bean Managed Transactions):

@Stateful
@TransactionManagement(BEAN)
public class MySessionBean implements ... 
{
  @Resource private UserTransaction ut;
  ... 
}

Попытка получить ссылку на интерфейс UserTransaction для компонента в режиме CMT (Container Managed Transactions) – либо с помощью dependency injection, либо с помощью JNDI/SessionContext, приведет к исключению IllegalStateException.

Конечно, если для данного session-компонента (или его конкретного метода) указаны атрибуты транзакции Required, Requires New или Mandatory (например, с помощью аннотации @TransactionAttribute), то можно быть уверенным, что глобальная транзакция существует, а если заданы атрибуты Not Supported и Never – то нет. К сожалению (применительно к данному вопросу), возможен еще атрибут Supports.

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

Но зачем программисту в данном случае – при использовании контекстов персистентности, управляемых приложением, знать о глобальных транзакциях?

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

@Stateful
public class MyStatefulBean implements MyBusinessInterface 
{
  @PersistenceUnit
  private EntityManagerFactory p_factory;
  private EntityManager p_manager;

  @PostConstruct
  public void сonstruct()
  {
//  Явное создание управляемого приложением менеджера (и его контекста)
    p_manager = p_factory.createEntityManager();
  }

  @PreDestroy
  public void destroy()
    p_manager.close();
  }

  public void myBusinessMethod () 
  {
//  Попытка сопоставить созданный контекст с глобальной транзакцией.
//  Если глобальной транзакции не существует, будет возбуждено исключение
//  TransactionRequiredException.

    p_manager.joinTransaction();
    ...
    em.persist(objRef);
    ...
//  При завершении глобальной транзакции данные managed-объектов 
//  будут записаны в БД.
  }
  ...
}

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

Еще один неочевидный аспект работы контекстом, управляемых приложением – смысл вызова метода clo­se() интерфейса EntityManager.

В приведенном выше примере контекст персистентности закрывался в callback-методе destroy(). Но программист может вызвать метод close() интерфейса EntityManager и в коде метода компонента, возможно, еще до завершения транзакции. При этом нужно понимать, что вызов close() не обязательно сразу приводит к закрытию управляемого приложением EXTENDED-контекста. Если контекст сопоставлен с транзакцией, то он не будет закрыт до ее завершения. Даже после вызова close() managed-объекты контекста остаются в этом состоянии (до завершения транзакции), а пользователь может вызывать для «закрытого контекста» такие методы, как getTransaction() и isOpen() (который вернет значение false). Вызов других методов приведет к исключению IllegalStateException.

Entity-листенеры

Использование listener-интерфейсов и объявленных в них callback-методов не является исключительной особенностью ни entity-сущностей, ни их применения в среде JavaEE. Напротив, стиль, синтаксис и «идеология» их задания вполне единообразна и для session-компонентов, и для entity-сущностей, хотя поведение программы для entity-классов в средах JavaEE и JavaSE имеет определенные отличия. Способ задания listener-интерфейсов для Entity-классов и их методов – с помощью как аннотаций, так и тегов XML-дескрипторов выходит за рамки данной статьи.

Применение Entity-листенеров в JavaEE позволяет усилить уровень контроля над выполнением программы в случае, когда транзакциями и контекстами персистентности управляет контейнер EJB, а не само приложение. Callback-методы тесно связаны с вызовом (явным или неявным) таких методов интерфейса EntityManager, как persist(), merge(), remove() – в общем, с изменением состояния объектов относительно контекстов персистентности. Программист может отслеживать следующие события цикла жизни объектов:

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

В любом случае разработчику не следует в коде callback-методов вызывать методы интерфесов EntityManager и Query – они будут выполнены, но приложение станет непереносимым между различными реализациями провайдера персистентности.

Задание параметров JPA в JavaEE-приложении

Как и в среде JavaSE, в JavaEE-приложении разработчик должен определить модуль персистентности, создав файл persistence.xml (и, возможно, другие файлы – например, orm.xml). Особенностью JavaEE-приложений являются:

Проще всего дело обстоит с координатором глобальных транзакций. Такой координатор всегда входит (как один из сервисов) в состав JavaEE-сервера приложений, поэтому программисту достаточно в файле persistence.xml указать режим использования JTA-транзакций:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

  <persistence-unit name="my_jta_unit" transaction-type="JTA">
    ...
  </persistence-unit>
</persistence>

Сложнее дело обстоит с заданием БД, в которых будут сохраняться состояние экземпляров Entity-классов.

Спецификация предполагает, что провайдеры персистентности в среде JavaEE для чтения и записи данных в БД буду использовать JNDI – для получения ссылок на реализуемые JDBC-драйверами интерфейсы DataSource. Для этого в файле persistence.xml предусмотрены теги <jta-data-source> и <non-jta-data-source>. Первый задает JNDI-имя источника данных, который поддерживает соединения, сопоставленные с контекстом глобальной транзакции. Тип фабрики таких физических соединений с БД имеет имя XADataSource, и этот интерфейс реализуется JDBC XA-драйверами. Второй тег задает JNDI-имя источника данных, который обеспечивает «обычные» JDBC-соединения, не может участвовать в распределенных транзакциях и не поддерживает двухфазное завершение транзакций.

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

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

  <persistence-unit name="my_jta_unit" transaction-type="JTA">
    ...
    <jta-data-source>
      jdbc/MyDB
    </jta-data-source>
  </persistence-unit>
</persistence>

Сложность состоит в том, что спецификации Java не оговаривают стандартные способы задания JNDI-имен и даже способ включения нужных источников данных в состав JavaEE-сервера. Это выполняется по-разному для различных реализаций JavaEE-серверов, и программисту приходится при работе с каждым конкретным сервером узнавать, как это нужно сделать.

Пример задания источника данных c использованием реляционной СУБД Derby для JavaEE-сервера приложений Geronimo 2.1 будет приведен ниже.

Наконец, формат JavaEE-приложения и место описание модуля персистентности в нем.

Модуль персистентности как реальные элементы приложения – это XML-дескрипторы, jar-файлы необходимых библиотек и байт-код Entity-классов, возможно, также упакованные в jar-файлы. Другими словами, обычные элементы современных Java-приложений. Следовательно, включение модулей персистентности в состав JavaEE-приложений разных видов не представляет никаких трудностей. Более того, существует много вариантов сделать это.

Другое дело, что «можно сделать» не всегда означает «нужно сделать» или «разумно сделать».

Возьмем, к примеру, клиентское приложение JavaEE. Это приложение, выполняемое в отдельном контейнере (возможности которого очень сильно зависят от конкретной реализации), и главное его достоинство – возможность настройки этого приложения с помощью изменения параметров XML-дескрипторов, без изменения и перекомпиляции исходного Java-кода, а также возможность организации взаимодействия элементов программы с помощью JNDI как сервиса, имеющегося в составе контейнера. Какие преимущества может дать использование JPA в таком приложении, которое, несмотря на свой JavaEE-формат, все равно остается клиентским?

Для такого приложения не характерно использование глобальных транзакций как системы «синхронизирующих сигналов» для выполнения действий, нет в нем и проблемы обеспечения целостности данных в том смысле, в котором используется термин «Consistency» в определении транзакций (ACID). Если в клиентском приложении используется БД (как правило – встроенная БД, функционирующая не как отдельно исполняемый сервер, а как набор классов Java, загруженных в ту же самую JVM, в котором выполняется само приложение), то можно получить выгоды от использования JPA, но это будет использование JPA в стиле, характерном для JavaSE, а не JavaEE – с использованием управляемым приложением EXTENDED-контекстов и локальных менеджеров транзакций.

Или возьмем Web-приложения (любое современное web-приложение в мире Java является JavaEE-приложением в смысле своего формата и структуры).

Web-приложения как составные части JavaEE-приложений предназначены для выполнения вполне определенной задачи – передачи получаемых с помощью WWW-инфраструктуры клиентских запросов серверным компонентам, реализующим бизнес-логику систему, реализацию web-сервисной части приложения и реализации презентационной логики, т.е. трансформации данных из одного представления в другое. Все эти задачи имеют весьма отдаленное отношения к систематическому выполнению объектно-реляционного отображения. Кроме того, на уровне Web-компонентов сложно говорить (в общем случае) о систематическом и универсальном использовании глобальных (JTA или JTS) транзакций.

Вследствие самой архитектуры распределенных JavaEE-систем, использование JPA в JavaEE- режиме наиболее естественно выглядит в модулях, которые реализуют серверную бизнес-логику, и JPA нужен для кэширования на уровне сервера приложений данных их БД, причем эти данные не связаны ни с каким конкретным клиентом, ни с конкретным сеансом работы, ни только с к конкретной частью бизнес-логики системы. Другими словами, JPA поставляет «сырые данные» для действий, выполняемых EJB-компонентами (session- и MDB-компонентами). При этом EJB-контейнеры (очень редко – сами EJB-компоненты) управляют синхронизацией состояния кэшированных данных в памяти и в БД – считывая информацию из БД и сохраняя в БД внесенные в процессе реализации бизнес-логики изменения.

Поэтому модули персистентности (реально используемые в режиме JavaEE, а не просто включенные в состав JavaEE-приложений) в большинстве случаев содержатся в модулях EJB-JAR – либо развернутых независимо (достаточно редкий случай), либо включенных в состав EAR-приложения (стандартный формат JavaEE-приложения).

Рассмотрим, как модуль персистентности включается в состав EJB-JAR-модуля.

Корневой каталог EJB-JAR-модуля называется «корнем» (root) модуля персистентности. Корень модуля должен содержать подкаталог META-INF, который содержит, наряду с XML-дескрипторами EJB-компонентов, файл XML-дескриптора модуля персистентности persistence.xml, а также другие XML-файлы, имеющие отношение к модулю персистентности, например, orm.xml.

В одном файле persistence.xml (находящемся в модуле EJB-JAR) могут быть объявлены несколько модулей персистентности, отличающиеся друг от друга именами. Эти модули доступны только EJB-компонентам, находящимся в том же EJB-JAR-модуле. Если приложение развертывается как EAR-модуль, то в нем могут находиться несколько модулей EJB-JAR, каждый со своими «непересекающимися» модулями персистентности. Разумеется, в разных EJB-JAR-модулях могут быть объявлены различные модули персистентности с одинаковыми именами – конфликта в этом случае не возникнет.

Байт-код Entity-классов, входящих в «локальный» (т.е. видимый только в пределах данного EJB-JAR-модуля) модуль персистентности, находится в подкаталогах корня модуля персистентности, определяемых именами пакетов Java – «вперемежку» с классами и интерфейсами для EJB-компонентов и просто классами Java.

Для удобства и обеспечения переносимости лучше все классы, входящие в модуль персистентности и расположенные в его корне (применительно к модулям EJB-JAR – в корневом каталоге соответствующего JAR-файла), явно перечислять в файле persistence.xml в тегах <class>.

Тем не менее, в JavaEE программист не обязан перечислять в файле persistence.xml все Entity-классы, входящие в соответствующий модуль персистентности – все классы Java с аннотацией @Entity, находящиеся в корне модуля персистентности, трактуются как входящие в этот модуль по умолчанию.

В модуль персистентности могут входить также классы (аннотированные как @Entity), которые находятся не в корне этого модуля, а в jar-файлах, расположенных в корне модуля. Но для этого в файле persistence.xml эти jar-файлы должны быть явно указаны в теге <jar-file>.

Наконец, в модуль персистентности входят также классы, которые объявлены в файле <root>/META-INF/orm.xml или других XML-файлах, задающих схему объектно-реляционного отображения между классами Java и таблицами реляционной СУБД. Например, если в файле <root>/META-INF/orm.xml содержится такие объявления:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
    http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">
  <entity class="demo.Test" access="PROPERTY">
    <table name="APP.TEST">      
    </table>
    <attributes>
      <id name="intkey"></id>
    </attributes>
  </entity>
</entity-mappings>

, то класс demo.Test включается в модуль персистентности вне зависимости от других объявлений или наличия/отсутствия в нем аннотаций, имеющих отношение к JPA.

Для задания дополнительных файлов объектно-реляционного отображения, помимо <root>/META-INF/orm.xml, в файле persistence.xml предусмотрены теги <mapping-file>. В качестве элемента такого тега задается имя файла отображения (относительно корневого каталога EJB-JAR-файла).

Если разработчик не хочет, чтобы аннотированные как @Entity классы, находящиеся в корне модуля персистентности, но не указанные ни в тегах <class>, ни явно упомянутые в orm-файлах, ни находящиеся в jar-файлах, явно перечисленных в тегах <jar-file>, входили в данный модуль персистентности, он может в файле persistence.xml задать тег вида:

  <exclude-unlisted-classes>true</exclude-unlisted-classes>

Пример:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="my_jta_unit" transaction-type="JTA">
    <provider>
      org.apache.openjpa.persistence.PersistenceProviderImpl
    </provider>
    <jta-data-source>
      jdbc/MyDB
    </jta-data-source>
    <class>demo.Test</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
  </persistence-unit>
</persistence>

Такие объявления говорят, что в единственный заданный для данного EJB-JAR-модуля модуль персистентности с именем my_jta-unit входят только класс demo.Test (вне зависимости от отсутствия или наличие в нем аннотации @Entity), а также классы, явно упомянутые в файле <root>/META-INF/orm.xml (если этот файл вообще существует). Никакие другие классы – опять-таки, вне зависимости от наличия или отсутствия аннотации @Entity – в данный модуль не входят.

Как правило, JavaEE-приложения развертываются в виде совокупности различных JavaEE-модулей (EJB-JAR, WAR, RAR – если говорить только о стандартных) и просто jar-файлов. Смысл – обеспечение эффективного доступа различных модулей друг к другу (например, обеспечение обычного, а не удаленного, обращения из кода web-компонента в WAR-модуле к EJB-компоненту в EJB-JAR-модуле), а также объявления «глобальных» ресурсов, т.е. ресурсов, доступных всем модулям.

Само по себе объединение, например, двух EJB-JAR-моделей (каждый со своим модулем персистентности) в одно EAR-приложение не делает модуль персистентности из одного EJB-JAR-модуля доступным для другого. Чтобы модуль персистентности был доступен нескольким EJB-JAR-модулям, его нужно объявить на уровне EAR-приложения. При таком подходе элементы модуля персистентности обязательно нужно упаковать в jar-файл. Для такого архива нет стандартного имени, но для удобства в данной статье мы назовем этот jar-файл PAR-модулем. Почему бы нет?

Чтобы корневой каталог PAR-модуля стал корнем объявленного в нем модуля персистентности, PAR-модуль должен находиться либо в корневом каталоге EAR-архива, либо в его подкаталоге /lib.

Разница между модулем персистентности в PAR-модуле и модулем персистентности в EJB-JAR-модуле заключается, таким образом, только в виде jar-файла – в первом случае используется «обычный» jar-файл, во втором – jar-файл, содержащий еще и обязательные XML-дескрипторы для EJB-компонентов.

Всегда ли модули персистентности, объявленные на уровне EAR-архива, доступны для EJB-JAR-модулей в составе этого приложения?

Если имена всех модулей персистентности для приложения различны, то всегда. Но возможна следующая ситуация: например, один персистентный модуль с именем MyPersistentUnit объявлен в некотором EJB-JAR-модуле, а второй с таким же именем – в PAR-модуле, например, в файле my_pmodule.jar, находящемся в подкаталоге /lib корневого каталога EAR-приложения. В этом случае персистентный модуль уровня EAR-при­ло­жения будет недоступен для упомянутого EJB-JAR-модуля.

Такого совпадения имен, разумеется, следует избегать, но если это все-таки произошло, то можно использовать «расширенный синтаксис» указания имени нужного модуля персистентности. Этот синтаксис можно использовать как в аннотациях @PersistenceUnit и @PersistenceContext (параметр unitName), так и в XML-дескрипторах для EJB-компонентов (тег <persistence-unit-name>). Например, для session-компонента MySessionBean ссылка на модуль персистентности MyPersistenceUnit уровня EAR-приложения (а не уровня EJB-JAR модуля, в котором объявлен этот session-компонент), может выглядеть так:

  @PersistenceContext(unitName=”../lib/my_pmodule.jar#MyPersistenceUnit”) 
  private EntityManager em;

На уровне XML-дескриптора связь между логическим JNDI-именем модуля персистентности и его именем в соответствующем файле persistence.xml можно задать, например, следующим образом.

Файл ejb-jar.xml для нашего EJB-JAR-модуля:

  ...
  <enterprise-beans>
    <session>
      <ejb-name>MySessionBean</ejb-name>
      <ejb-class>
        demo.MySessionBean
      </ejb-class>
      ...
      <persistence-unit-ref>
        <persistence-unit-ref-name>
          persistence/MyPModule
        </persistence-unit-ref-name>
        <persistence-unit-name>
          ../lib/my_pmodule.jar#MyPersistenceUnit
        </persistence-unit-name>
      </persistence-unit-ref>
      ...
    </session>
  </enterprise-beans>
  ...

Путь к нужному jar-файлу задается относительно корневого каталога модуля, из которого происходит обращение. В нашем примере контекст персистентности используется компонентом, находящимся в EJB-JAR модуле в составе EAR-приложения. Строка

../lib/my_pmodule.jar#MyPersistenceUnit

интерпретируется так: перейти на уровень вверх – в корневой каталог EAR-архива, затем перейти в его подкаталог /lib, найти в нем файл my_pmodule.jar, где в подкаталоге /META-INF искать файл persistence.xml, в котором объявлен модуль персистентности MyPersistenceUnit.

Для WAR-модулей и клиентских JAR-модулей JavaEE определены похожие правила. Подробное описание можно найти в спецификации JPA на сайте java.sun.com.

Создание JavaEE-приложения с использованием JPA для Eclipse и Geronimo

Создание приложения начнем с создания источника данных, который будет использоваться в приложении. В качестве БД будем использовать реляционную БД с открытым кодом Derby. Это полнофункциональная СУБД, которая может работать как в режиме удаленного сервера БД, так и как встроенная БД. Для Derby в обоих возможных режимах работы поставляются JDBC-драйвера, как с поддержкой распределенных глобальных транзакций (XA-драйвера), так и без нее. В примере будем использовать Derby в режиме встроенной БД с поддержкой JTA.

В качестве JavaEE-сервера приложений будет выступать также сервер с открытым исходным кодом Geronimo 2.1, который, как и Derby, создается и поддерживается в рамках проектов консорциума Apache. Geronimo – сертифицированный JavaEE-сервер, построенный по модульному принципу, на основе использования компонентной модели GBeans.

Создание и регистрация источника данных (JDBC-интерфейса DataSource)

В состав консоли администратора Geronimo входит специальный эксперт, который создает модуль для создаваемого источника данных. Создание источника данных выполняется в виде разработки стандартного RAR-модуля со своим XML-дескриптором, который в Geronimo называется «планом развертывания» (deployment plan). Создать и развернуть источник данных для Geronimo – значит, собрать RAR-модуль, который содержит нужные для работы JDBC-драйверы, классы и план развертывания, затем скопировать собранный модуль в нужный каталог под управлением сервера (данный RAR-модуль будет считываться и загружаться автоматически при каждом запуске сервера), а также зарегистрировать этот источник (как интерфейс DataSource) в экземпляре JNDI, входящем в состав Geronimo. После того, как источник данных создан, к нему можно обратиться по его JNDI-имени.

Как Geronimo, так и Derby можно загрузить с сайта www.apache.org. Поставляются они в виде архивов, содержащих как готовые к использованию бинарные сборки, так и исходный код (если пользователь хочет построить собственный бинарный дистрибутив.)

Для использования Derby и Geronimo архивы достаточно распаковать в каталоги на жестком диске. В подкаталогах /bin для обоих продуктов содержатся командные файлы для запуска и установки серверов.

Дистрибутив Derby понадобится вам только в случае, если вы захотите запустить эту БД не во встроенном режиме (как в рассматриваемом примере), а в виде независимо запускаемого сервера СУБД. Для использования Derby во встроенном режиме получать и распаковывать дистрибутив не нужно – Geronimo уже содержит все необходимое (а именно, нужные JDBC-драйвера).

Прежде всего, нужно запустить Geronimo (для этого достаточно) после распаковки полученного архива выполнить один из командных файлов запуска сервера, находящихся в каталоге /bin (для платформы Windows он имеет имя start-server.bat, для UNIX-платформ – start-server).

Запуск сервера займет несколько секунд. После того, как он будет запущен, нужно запустить консоль администратора. Для этого нужно в любом из наиболее распространенных Internet-браузеров набрать URL http://localhost:8080/console. В появившемся окне появится запрос на ввод идентификатора и пароля пользователя. В качестве идентификатора введите «system», в качестве пароля – «manager». Появится следующая страница (рисунок 1):


Рисунок 1. Окно администратора Geronimo.

Для создания модуля источника данных нужно в левой части консоли выбрать пункт Database Pools. Появится список установленных модулей для источников данных, а также команда для запуска эксперта создания нового источника (Using the Geronimo Database pool wizard):


Рисунок 2. Установленные модули источников данных.

Выберите команду «Using the Geronimo...», появится первая страница работы с экспертом – выбор вида БД.


Рисунок 3. Эксперт создания модуля источника данных. Выбор БД.

В первое поле – поле имени – нужно ввести имя, которое станет относительным JNDI-именем для создаваемого в модуле источника данных. Это имя принято создавать в контексте имен jdbc – просто для того, чтобы удобно было сразу видеть, к какому именно виду ресурса относится имя. Это не полное JNDI-имя в глобальном JNDI-контексте – полное имя мы увидим позже, после развертывания создаваемого модуля на сервере.

Для перехода к следующему этапу нужно нажать кнопку Next. В появившемся окне нужно задать параметры создаваемого источника данных (они зависят от типа выбранной БД):


Рисунок 4. Эксперт создания модуля источника данных. Параметры источника данных.

Для выбранной БД прежде всего нужно указать jar-файл, содержащий классы нужного JDBC-драйвера. В общем случае эти jar-файлы должны быть (с соблюдением специальных правил именования) помещены в подкаталоги каталога <install_dir>/repository, где <install_dir> – каталог установки сервера Geronimo. При использовании Derby задача упрощается – Geronimo сам использует БД Derby для своих целей, поэтому вместо jar-файла можно указать (эксперт это делает сам) уже развернутый на сервере специфический для этого сервера CAR-модуль, содержащий все необходимые классы для работы с Derby.

Под именем БД («Database Name») применительно к Derby понимается каталог на диске, в котором сервер БД будет создавать файлы данных. В данном примере эта БД уже существует (она использовалась в примерах предыдущей статьи), поэтому параметр «Create Database» имеет значение «false». Если при работе с БД одновременно будет использоваться не более одного соединения, то имя пользователя и пароль можно указать любые. Для остальных параметров можно принять значения по умолчанию.

Ниже заполняемых полей находятся две кнопки – Deploy и Show Plan (их не видно на рисунке 4). При нажатии на кнопку Deploy буден сгенерирован XML-дескриптор для модуля (план развертывания), и модуль будет установлен на сервере. Это не совсем удобно – параметры самого RAR-модуля будут выбраны по умолчанию, кроме того, возможно, разработчик непосредственно перед развертыванием захочет что-то изменить, поэтому лучше нажать кнопку Show Plan. Появится текст плана развертывания, который можно скопировать в отдельный файл, отредактировать его, а затем выполнить установку модуля на сервере «вручную» или с использованием других средств.

Для изменения каких-либо ранее введенных значений (не текста плана развертывания) нужно нажать кнопку «Edit Settings».

Сгенерированный файл плана развертывания, соответствующий введенным параметрам, будет иметь следующий вид:

<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://geronimo.apache.org/xml/ns/j2ee/connector-1.2">
  <dep:environment 
    xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2">
  <dep:moduleId>
    <dep:groupId>console.dbpool</dep:groupId>
    <dep:artifactId>jdbc_MyDB</dep:artifactId>
    <dep:version>1.0</dep:version>
    <dep:type>rar</dep:type>
  </dep:moduleId>
  <dep:dependencies>
    <dep:dependency>
      <dep:groupId>
         org.apache.geronimo.configs
      </dep:groupId>
      <dep:artifactId>
        system-database
      </dep:artifactId>
      <dep:version>2.1.4</dep:version>
      <dep:type>car</dep:type>
    </dep:dependency>
  </dep:dependencies>
  </dep:environment>
  <resourceadapter>
    <outbound-resourceadapter>
      <connection-definition>
        <connectionfactory-interface>
          javax.sql.DataSource
        </connectionfactory-interface>
        <connectiondefinition-instance>
          <name>jdbc/MyDB</name>
        <config-property-setting
          name="DatabaseName">
          D:\Software\Derby_DBs\jpa_embedded
        </config-property-setting>
        <config-property-setting 
          name="CreateDatabase">
          false
        </config-property-setting>
        <config-property-setting 
          name="Password">
          alex_password
        </config-property-setting>
        <config-property-setting 
          name="UserName">
          alex
        </config-property-setting>
        <config-property-setting 
          name="LoginTimeout"/>
        <connectionmanager>
          <xa-transaction>
            <transaction-caching/>
          </xa-transaction>
          <single-pool>
            <max-size>10</max-size>
            <min-size>0</min-size>
            <match-one/>
          </single-pool>
        </connectionmanager>
        </connectiondefinition-instance>
      </connection-definition>
    </outbound-resourceadapter>
  </resourceadapter>
</connector>

Жирным шрифтом выделены параметры созданного модуля (они понадобятся позже) и относительное JNDI-имя для созданного источника данных.

Для завершения создания модуля и развертывания его на сервере нажмите кнопку «Deploy Pool» (рисунок 5).


Рисунок 5. План развертывания модуля источника данных.


Рисунок 6. JNDI Viewer консоли администратора Geronimo.

На странице JNDI Viewer консоли администратора Geronimo можно увидеть, какое глобальное JNDI-имя присвоено созданному источнику данных:

Теперь можно приступить к созданию JavaEE-при­ложения, в котором будут присутствовать модуль персистентности, session-компоненты и Entity-классы.

Параметры проекта Eclipse для использования Geronimo, EJB и JPA

Одним из достоинств IDE (Eclipse в том числе) для разработки, запуска и тестирования приложения является интеграция с серверными средами исполнения, которая автоматизирует процесс развертывания и запуска серверных приложений, в данном случае – JavaEE-при­ложений с использованием EJB и JPA на JavaEE-сервере Geronimo.

Прежде всего, необходимо установить сборку Eclipse, которая поддерживает разработку JavaEE-приложений, например, Eclipse JavaEE Developer.

Затем нужно с сайта обновления Geronimo загрузить плагины для совместной работы Eclipse и Geronimo. Проще и удобней сделать это с сайта обновлений IBM – можно загрузить сразу плагины для работы не только с Geronimo, но и с IBM WebSphere CE – версию Geronimo, поддерживаемую IBM. URL для загрузки плагинов от IBM – http://download.boulder.ibm.com/ibmdl/pub/softwa­re/­web­sphere/wasce/updates/.

На рисунке 7 приведен список плагинов, требующихся для удобной работы с Geronimo и IBM WAS CE.


Рисунок 7. Плагины для поддержки Geronimo/WAS CE в среде Eclipse.

Следующий шаг – настройка взаимодействия установленного экземпляра с Geronimo. Это делается при переходе в окно Preferences Eclipse (например, по команде главного меню Window > Preferences):


Рисунок 8. Окно настроек Preferences для установленного экземпляра Eclipse.

Для установки параметров новой среды нажмите кнопку «Add» и следуйте указаниям эксперта настройки.

После задания всех настроек можно перейти к созданию проекта.

Работа с таким проектом в среде Eclipse – точнее, использование экспертов Eclipse – требует задания ряда опций проекта и настроек среды (хотя без них можно, в принципе, и обойтись). Иногда важна и последовательность действий. Например, расширить EJB-проект для поддержки JPA не составляет никакого труда (нужно просто в готовый EJB-проект добавить фасет для поддержки JPA), но не наоборот – добавить поддержку EJB в JPA-проект гораздо труднее. Если же разработчик хочет использовать эксперты Eclipse для создания Entity-сущностей, то он должен еще и задать параметры соединения с нужной БД. Это можно сделать заранее, а можно – непосредственно при работе с экспертом. В любом случае для этого необходимо открыть представление (view) Data Source Explorer.

Соединение, которое будет задействовано в данном примере, имеет следующие свойства (рисунок 9):


Рисунок 9. Параметры соединения с БД.

Для создания проекта нужного типа удобнее всего использовать эксперт EJB Project (рисунок 10):


Рисунок 10. Выбор эксперта создания проекта нужного типа.

При нажатии на кнопку Next в появившемся окне нужно задать имя проекта, используемую версию EJB (3 или 2.x), сервер, на котором будет развернуто приложение, и т.д. (рисунок 11):


Рисунок 11. Основные параметры создаваемого проекта.

На последнем шаге эксперт запросит ввести параметры создаваемого CAR-модуля Geronimo – специфического для этого сервера формата модуля JavaEE-приложения. К этим параметрам относятся GroupId, Artifact Id, Version и Artifact Type (car, по умолчанию). Большого смысла эти значения не имеют, но лучше, если разработчик задаст имена Group Id и Artifact Id, по которым эту конфигурацию легко будет отличить от других.


Рисунок 12. Фасеты проекта.

Эти параметры будут использованы средой Eclipse при выполнении процедуры развертывания приложения и на сервере. Они сохраняются в нестандартном XML-дескрипторе Geronimo META-INF/openejb-jar.xml.

Созданный проект «умеет работать» с EJB-ком­понентами и экспертами для их создания, но, как видно из рисунка 10, не с JPA и Entity-сущностями. Чтобы обеспечить поддержку JPA на уровне экспертов, нужно изменить свойство созданного проекта, добавив в него фасет для JPA:

Добавление фасета Java Persistence приводит к добавлению в проект файла META-INF/persistence.xml, а также возможности использовать эксперты Eclipse по созданию Entity-сущностей в составе данного проекта. Entity-сущности будут добавляться в EJB-JAR-модуль, в добавление к EJB 3-компонентам.

После добавления в проект session-компонента MySta­te­lessBeanWContext, его удаленного бизнес-интер­фейса MyRemoteBusinessInterface, клиентского приложения для тестирования компонента RemoteClient и Entity-класса Test, структура проекта может выглядеть так, как показано на рисунок 13:


Рисунок 13. Структура проекта.

Ниже приведен код XML-файлов для такого проекта (только тех, которые содержат полезную информацию).

Файл persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="my_jta_unit" transaction-type="JTA">
    <provider>
      org.apache.openjpa.persistence.PersistenceProviderImpl
    </provider>
    <jta-data-source>
      jdbc/MyDB
    </jta-data-source>
    <class>demo.Test</class>
    ...
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
  </persistence-unit>
</persistence>

Файл orm.xml:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">

  <entity class="demo.Test" access="PROPERTY">
    <table name="APP.TEST">      
    </table>
    <attributes>
      <id name="intkey"></id>
    </attributes>
  </entity>
  ...
</entity-mappings>

Файл openejb-jar.xml (в этом файле прописана зависимость созданной конфигурации от RAR-модуля, содержащего определение источника данных):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ejb:openejb-jar xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2" xmlns:ejb="http://openejb.apache.org/xml/ns/openejb-jar-2.2" xmlns:name=http://geronimo.apache.org/xml/ns/naming-1.2
  ...
  <dep:environment>
    <dep:moduleId>
      <dep:groupId>stateless_jpa_group</dep:groupId>
      <dep:artifactId>sgId</dep:artifactId>
      <dep:version>1.0</dep:version>
      <dep:type>car</dep:type>
    </dep:moduleId>
    <dep:dependencies>
      <dep:dependency>
        <dep:groupId>console.dbpool</dep:groupId>
        <dep:artifactId>jdbc_MyDB</dep:artifactId>
        <dep:version>1.0</dep:version>
        <dep:type>rar</dep:type>
      </dep:dependency>
    </dep:dependencies>
  </dep:environment>
</ejb:openejb-jar>

Примеры использования классов и методов JPA были приведены в предыдущей статье.

Наконец, для тестирования созданных EJB-компонентов и Entity-сущностей можно создать клиентское (JavaSE) приложение примерно следующего вида:

package demo;

import javax.naming.*;

import java.util.*;
import java.util.concurrent.TimeUnit;

public class RemoteClient 
{

  public static void main(String[] args) 
  {
    try
    {
      Properties props = new Properties();
      props.setProperty(Context.INITIAL_CONTEXT_FACTORY,
        "org.openejb.client.RemoteInitialContextFactory");
      props.setProperty(Context.PROVIDER_URL, "localhost:4201");

      Context initContext = new InitialContext(props);

      MyRemoteBusinessInterface objRef = 
        (MyRemoteBusinessInterface)initContext.lookup
        ("MyStatelessBeanWContextRemote");
      ...
    }
    catch (Exception e)
    {
      ...
    }
  }
}

Для того, чтобы можно было на стороне клиента выполнить преобразование типов и сгенерировать код клиентской заглушки, в проект нужно включить файл openejb-client-3.0.1.jar, расположенный в каталоге <Geronimo_install_dir>\repository\org\apache\openejb\openejb-client\3.0.1.

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

@Stateless
@Remote (MyRemoteBusinessInterface.class)
public class MyStatelessBeanWContext implements MyRemoteBusinessInterface 
{

  @PersistenceContext (type=PersistenceContextType.TRANSACTION)
  private EntityManager em;

  DataSource ds;

  public MyStatelessBeanWContext() 
  {
    Context context = null;
    try 
    {
      context = new InitialContext();
      ds = (DataSource)context.lookup
  ("jca:/console.dbpool/jdbc_MyDB/JCAManagedConnectionFactory/jdbc/MyDB");
    }
    catch (NamingException e) 
    {
      ...
    }
  ...
}

Заключение

JPA как обобщение опыта ряда нестандартных технологий применительно к Java стал основным инструментом выполнения объектно-реляционного отображения и кеширования состояния объектов из БД на уровне серверов приложения, фактически объявив устаревшими Entity-компоненты EJB 2.1. При этом Entity-сущности JPA вполне обоснованно можно трактовать как развитие основных идей, в свое время приведших к созданию Entity-компонентов.


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

Copyright © 1994-2016 ООО "К-Пресс"