Джефф Хэмбрик, инженер, IBM
В каждой статье EJB Advocate приводится типичный диалог с реальными пользователями и разработчиками в процессе предоставления рекомендаций по решению какой-либо интересной проблемы. Персональные данные участников диалога не сообщаются, также не используются недостаточно испытанные и закрытые архитектуры.
Эй, EJB Advocate!
Пора просыпаться - кофе готов! EJB 2.x умерла! Да здравствует EJB 3! Посмотрите, насколько упрощается жизнь при создании персистентного объекта, такого, например, как customer в одной из Ваших предыдущих статей:
@Entity
@Table(name="CUSTOMER")
public class Customer implements Serializable {
private int id;
private String name;
private Order openOrder;
public Customer () {
super();
}
@Id
@Column(name="CUST_ID")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name="NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER )
@JoinColumn(name="OPEN_ORDER_ID",referencedColumnName="ORDER_ID")
public Order getOpenOrder() {
return openOrder;
}
public void setOpenOrder(Order openOrder) {
this.openOrder = openOrder;
}
} |
Нужно всего лишь создать простой POJO и аннотировать его. Не нужно ничего наследовать. Не требуется отдельный home, интерфейс или дескриптор развертывания. Кроме того, не нужно выполнять действия по отображению для конкретного производителя программного обеспечения!
Код в клиентской программе, использующей EJB 3, еще проще. Допустим, я хочу получить открытый заказ, связанный с клиентом, ключом которого является customerId с типом integer:
@PersistenceContext (unitName="db2")
EntityManager em;
try {
Customer c = (Customer)em.find(
Customer.class, new Integer(customerId)
);
Order o = c.getOpenOrder();
return o;
}
catch (EntityNotFoundException e) {
throw new CustomerDoesNotExist(customerId);
} |
Поскольку один экземпляр EntityManager выступает в роли home для всех сущностей, не требуется JNDI-поиск. Нет ничего проще.
Так скажите, зачем есть гамбургер после бифштекса?
Never Going Back (Никогда не возвращаться назад)
Уважаемый Never Going Back!
Поскольку тон Вашего письма несколько нагловат, язвительно замечу, что EJB-компонент управления данными, о котором Вы говорите, на самом деле представляет собой часть интерфейса прикладного программирования Java Persistence API (JPA), являющегося отдельной, но связанной спецификацией JSR 220. Поэтому было бы корректнее сказать: "использовать JPA проще".
Если серьезно, то я рад, что Вы нашли стандартный подход к персистенции, который Вас устраивает. Очень многие мои собеседники заканчивали тем, что изобретали свой собственный подход к персистенции. Я полагаю, что слово "назад" в Вашей подписи "Никогда не возвращаться назад" означает, что вы не один из них.
Рискуя вызвать Ваше неудовольствие, EJB Advocate собирается очень тщательно проанализировать указанные Вами преимущества, чтобы Вы могли правильно оценить выгоды JPA в сравнении с CMP-компонентами управления данными.
-
JPA-компоненты управления данными - это не POJO
Вначале Вы упомянули, что просто создаете POJO и аннотируете его (в числе прочего используя @entity) для создания компонента в стиле JPA. Все правильно. Но справедливости ради надо сказать, что большинство не считает механизм персистенции основанным на POJO, пока он не может быть использован с существующим (не модифицированным) простым Java-классом.
Можно также вспомнить времена EJB 1.x, когда компоненты управления данными были больше похожи на POJO. Переменные экземпляра объявлялись как часть реализации компонента, который был конкретным классом. Вы "аннотировали" класс путем объявления его EJB-компонентом управления данными и реализовали методы управления жизненным циклом, такие как ejbLoad() и ejb Store() (или оставляли их для инструментальных средств развертывания). Одной из причин отказа от них явилось то, что они загружали CMP-поля по принципу все или ничего. Подход EJB 2.x разрешает гибко улучшать производительность, например, отображать методы get<Property>() на курсор базы данных или реализацию потока, уменьшая количество преобразований. JPA разрешает "ленивую" загрузку свойств через аннотацию, но как мы увидим далее, этот подход создает свои собственные проблемы, если полагать, что логический объект является POJO-объектом.
-
JPA и EJB 2.x не требуют создавать подкласс (расширять) какого-либо конкретного класса
Затем Вы упомянули, что при использовании JPA ничего не нужно "наследовать", подразумевая, что логические объекты EJB 2.x это делают. Такое предположение ошибочно. Представьте типичное объявление класса компонента управления данными в EJB 2.x:
public abstract class CustomerBean implements EntityBean ... |
Класс (абстрактный или конкретный), реализующий интерфейс, на самом деле не наследует ничего из интерфейса, который он реализует. Реализация интерфейса - это просто маркер того, что класс может выполнять данную роль в приложении, аналогично действию, для которого предназначена аннотация @entity.
Эта отличительная особенность очень важна, поскольку в том, что касается реализации, Java поддерживает только одинарное наследование. Вы можете расширить только один класс и наследовать его реализации методов. Вы можете реализовать столько интерфейсов, сколько пожелаете.
А поскольку EJB 2.x-компоненты управления данными являются абстрактным классом, нет необходимости реализовывать какие-либо из различных методов жизненного цикла EJB, связанных с интерфейсом EntityBean, например, ejbLoad() и ejbStore(). Эти реализации могут быть возложены на инструментальные средства развертывания, предоставляемые поставщиками программного обеспечения.
-
JPA-компоненты управления данными не поддерживают несколько представлений
Далее Вы заметили, что не нужно создавать интерфейс или home в JPA-компонентах. Это правда. Но такая простота есть следствие компромисса: Вы напрямую обращаетесь к классу реализации. Ограничение заключается в том, что Вы получаете только одно представление компонента. Java-интерфейсы являются мощными в том плане, что можно предоставить представления компонентов реализации, настроенные под требования клиента. EJB 2.x-компоненты управления данными поддерживают концепцию отделения реализации от интерфейсов. Например, можно предоставить как удаленное, так и локальное представление логического объекта.
Почему Вы хотите предоставить удаленный интерфейс к EJB-компоненту управления данными, если наилучшей известной методикой является использование фасада? В предыдущей статье EJB Advocate мы рассказали, как использовать специализированные home-методы компонентов управления данными для выполнения роли встроенного фасада. Поскольку фасад часто используется удаленно, можно было бы предоставить удаленный домашний интерфейс, включающий один или несколько специализированных методов, например:
public interface CustomerHome implements EJBHome {
CustomerData ejbHomeGetOpenOrderForCustomer(int customerId) ...
} |
Обратите внимание на то, что этот удаленный интерфейс не имеет каких-либо методов create() или find(). Это преднамеренное их отсутствие гарантирует, что к экземпляру логического объекта Customer никогда нельзя обратиться удаленно. Локальный и домашний интерфейсы к этой же реализации компонента предоставляли бы методы create и find вместе с соответствующими методами get и set (которые, возможно, представляют собой CMR, как описано в еще одной статье EJB Advocate).
И, как уже говорилось, большинство инструментальных средств для EJB (например, программное обеспечение IBM Rational Application Developer for WebSphere) генерирует интерфейсы и home за один шаг при помощи программы Wizard. Некоторые даже предоставляют возможность начать с реализации компонента (например, POJO) и создавать методы для интерфейса и home по мере необходимости.
-
JPA-компоненты управления данными встраивают детали реализации в Ваши POJO-объекты
Еще одно "преимущество" JPA, упомянутое Вами, - возможность пропустить создание отдельного дескриптора развертывания и действия по отображению для конкретного поставщика программного обеспечения. Все это хорошо (особенно в том, что касается стандартных отображений на хранилища реляционных данных), но здесь имеет место компромисс. Вы встроили детали реализации в код, что сделало объект еще менее похожим на простой POJO, к которому Вы, по-видимому, стремитесь.
Одной из наилучших функциональных возможностей EJB 2.x-компонентов управления данными является то, что при развертывании можно изменить детали реализации персистенции данного бизнес-объекта абсолютно независимо от провайдера компонента. Нет причин, по которым провайдер не может принять аналогичный стандартный набор аннотаций для отображения как часть дескриптора развертывания в стиле EJB 2.x, чтобы упростить действия по отображению.
-
JPA-компоненты управления данными все еще лучше использовать за фасадом
Наконец, Вы правильно отметили, что имеется только одна реализация EntityManager, которая устраняет необходимость выполнять JNDI-поиск и чрезвычайно облегчает использование JPA-компонента управления данными. Однако Вы не упомянули, что если не использовать JPA-компонент в контексте транзакции, необходимо явно сохранять EntityManager для сохранения любых изменений. Еще один аспект JPA, о котором Вы забыли упомянуть: если объявить для компонента "ленивую" загрузку в аннотациях и затем отключить от контекста (то есть, конец транзакции), то нетронутые поля становятся неопределенными.
Необходимость явного управления контекстом транзакций могла бы усложнить программную модель JPA. Поэтому до сих пор наилучшей методикой остается использование сессионного фасада (в новом стиле EJB 3 или в старом EJB 2.x). Если Вы выберете возврат POJO из фасада, то Вам придется установить значение загрузки в "eager" (активную) или затронуть все поля в JPA-сущности. При использовании специализированных home-методов EJB 2.x сессия абсолютно не нужна.
Очевидно, что большинство преимуществ EJB 3 и JPA, о которых Вы упомянули, имеет упрощающие предположения, которые могут не работать во всех ситуациях. Вы должны сами решить, можете ли обойтись без этих предположений. Если это так, то, надеюсь, Вы видите, что почти все они могут быть применены к спецификации EJB 2.x через инструментальные средства развертывания. Наконец, даже спецификация JPA упоминает, что EJB 2.x все равно, вероятно, будет использоваться после завершения разработки EJB 3 и JPA.
Следовательно (да простит меня Марк Твен), EJB Advocate полагает, что слухи о кончине EJB 2.x сильно преувеличены.
Ваш EJB Advocate
В этом диалоге исследованы некоторые фундаментальные вопросы простоты. Например, что лучше: иметь сложную систему, обеспечивающую всю необходимую гибкость, или простую систему, облегчающую работу в стандартных ситуациях? В приведенных выше размышлениях EJB Advocate предложил: почему бы не предоставить то и другое, обеспечивая гибкость, но также и вариант по умолчанию, делающий вещи простыми?
Для тех, кто предпочитает объектно-ориентированный подход к разработке приложений, EJB 2.x все еще может быть более удобной, поскольку она использует все возможности Java, делающие его столь мощным языком программирования, особенно в плане отделения интерфейса от реализации.