Поисковые методы и EJB-QL


Вы, конечно, уже заметили, что есть только один способ для клиентского кода найти существующий экземпляра entity компонента: вызвать метод findByPrimaryKey(…), который подразумевает, что клиент обязан знать значение первичного ключа определенного фильма прежде, чем он сможет получить его. Это, конечно же, неприемлемо: даже если клиентское приложение на своей стороне сети будет хранить список первичных ключей для всех записей, которые он создал (уродливый подход), как он может получить первичные ключи для всех записей, которые были созданы другими клиентами на этом же самом сервере? Или другой пример, как вы можете получить ссылки на все фильмы, название которых содержит слово "Java"?

Ответ содержится в настраиваемых методах поиска. Концептуально поиск не сильно отличается от выполнения SQL запроса на реляционной базе данных: вы указываете некоторые критерии для поиска (выражение WHERE в SQL запросе), место поиска (выражение FROM) и то, что вас интересует (выражение SELECT), и вы получаете назад список записей, возможно пустой. Для EJB вы получаете не записи - фактически, вы даже не знаете о существовании реляционной базы данных - а ссылки на компонентные интерфейсы сущностей, которые вы ищите.

Думаю, что здесь есть другой открытый вопрос: как вы укажите критерии для поиска ваших сущностей, если вы даже не знаете, какого рода хранилище данных используется за кадром? Какой синтаксис вы предполагаете использовать для указания вашего критерия поиска? В EJB 1.1 различные поставщики имели разные способы для предоставления контейнеру такого рода информации, но в EJB 2.0 процесс был стандартизован путем введения языка запросов EJB (EJB Query Language).

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

Еще раз, у меня нет здесь места для детального объяснения EJB-QL, но я дам вам простой пример, который должен помочь прояснить концепцию. За дополнительной информацией я отсылаю вас к документам, перечисленным в конце этой главы. Теперь давайте взглянем на код сущностного компонента Movie, и на новые специфические тэги EJBDoclet'а (Java код не изменился):

//:example10/src/ejb-tier/javatheater/ejb/implementation/MovieBean.java
package javatheater.ejb.implementation;

import javax.ejb.*;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

/**
* Этот код реализации сущностного компонента
<code>Movie</code>.
*
*
@see javatheater.ejb.MovieHome
* @ejb:bean name="Movie" local-jndi-name="javatheater/Movie" view-type="local"
*           type="CMP" primkey-field="id" reentrant="False"
* @ejb:pk class="java.lang.Integer"
* @ejb:home local-class="javatheater.ejb.MovieHome"
* @ejb:interface local-class="javatheater.ejb.Movie"
* @ejb:finder signature="java.util.Collection findByTitle(java.lang.String
*             title)" query="SELECT OBJECT(m) FROM Movie m WHERE m.title=?1"
* @ejb:finder signature="java.util.Collection findAll()" query="SELECT
*             OBJECT(m) FROM Movie m"
* @ejb:env-entry name="CounterName" value="MoviePKCounter"
*                type="java.lang.String"
*/
public abstract class MovieBean implements EntityBean {
  
/**
    * Проверяем первичный ключ и инициализируем параметры компонента.
    *
    * @ejb:create-method
    */
  
public Integer ejbCreate(Integer id, String title) throws CreateException {
     
if (id == null)
        
throw new CreateException("Primary key cannot be null");
     
if (id.intValue() == 0)
        
throw new CreateException("Primary key cannot be zero");
      setId
(id);
      setTitle
(title);
     
return null;
  
}
  
  
/**
    * Ничего не делаем.
    */
  
public void ejbPostCreate(Integer id, String title) {
   }
  
  
/**
    * Домашний метод возвращает имя счетчика (String) для генерации уникального
    * значения первичного ключа фильма. Реальное имя определяется как
    * переменная окружения компонента Movie.
    *
    * @ejb:home-method
    */
  
public String ejbHomeGetCounterName() {
     
try {
        
Context initial = new InitialContext();
        
return (String) initial.lookup("java:comp/env/CounterName");
     
}
     
catch (NamingException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * Возвращает Movie id, который также является первичным ключем.
    *
    * @ejb:pk-field
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public Integer getId();
  
  
/**
    * Этот метод не является частью удаленного интерфейса но ejbdoclet будет
    * все равно генерировать его реализацию. Этот методы вызывается из
    * ejbCreate().
    */
  
abstract public void setId(Integer id);
  
  
/**
    * Возвращает заголовок фильма.
    *
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public String getTitle();
  
  
/**
    * Устанавливает заголовок фильма.
    *
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public void setTitle(String title);
}
// /:~

Мы использовали два новых тэга ejb:finder для EJBDoclet'а, чтобы он сгенерировал собственные методы поиска. Собственные поисковики являются методами домашнего интерфейса сущностного компонента, которые может использовать клиент для нахождения сущностей. До этого мы видели только один поисковик - это findByPrimaryKey(…), но EJB спецификация позволяет создать дополнительные поисковики, определяемые поставщиком компонента (bean provider). Только findByPrimaryKey(…) возвращает единственную ссылку на компонент; все остальные поисковики возвращают коллекцию Java - это имеет смысл, поскольку только первичный ключ может уникально идентифицировать одну сущность.

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

Метод поиска предназначен для получения информации из постоянного хранилища. Так как EJB спецификация позволяет разные типы хранилищ данных, реализация поисковиков может значительно различаться. Если вы реализуете поисковик для сущностного компонента BMP, вы отвечаете за реализацию всех деталей при доступе к базе данных и информировании ее правильным образом (что, без сомнения, может быть сложным, в зависимости от природы вашего сущностного компонента и его взаимосвязей с другими компонентами). С другой стороны, если вы реализуете сущностный компонент CMP, вы не кодируете для одной определенной базы данных (помните, что CMP существует для повышения уровня портируемости), но вам все равно необходимо указать критерии поиска.

Хорошая новость состоит в том, что контейнер генерирует реализацию поисковых методов за вас. Все, что вам нужно сделать, это сказать контейнеру, где он должен искать и что. Этот вопрос подводит нас к основной теме абстрактной схемы сущностей и EJB-QL, соответственно. Абстрактная схема является простой концепцией: ваш сущностный компонент представляет собой постоянный объект, так? Это означает, что его состояние должно сохраняться в некотором постоянном хранилище, и состояние должно быть организовано в некотором формате (таблицы и записи, например). Однако, платформа EJB изначально разрабатывалась независимой от хранилища данных. Так что нам необходим, как и всегда, когда вещи становятся сложными, дополнительный уровень абстракции, который позволит нам ассоциировать состояние сущностного компонента с постоянной информацией, и этот уровень абстракции называется схемой абстракции. Абстрактная схема, в свою очередь определяет информацию, которая может быть запрошена с помощью EJB-QL.

На практике, каждый сущностный компонент CMP имеет ассоциированную схему имен, которую вы можете использовать в выражениях EJB-QL, а компонент выдает информацию через свои свойства, как определено в спецификации JavaBean'ов. Прежде, чем вы продолжите читать, вам необходимо собрать example10, поскольку мы должны взглянуть на код, сгенерированный EJBDoclet'ом - используйте Ant скрипт как и всегда для построения примера.

Прежде всего, давайте взглянем на домашний интерфейс для сущностного компонента Movie, который вы найдете в gen/javatheater/ejb/MovieHome.java в директории example10. Вы сразу же заметите, что два новых тэга ejb:finder в исходном коде реализации компонента стали причиной того, что EJBDoclet сгенерировал два новых поисковых метода, в домашнем интерфейсе Movie.

Теперь давайте взглянем на дескриптор развертывания, расположенный в каталоге gen/META-INF. Если вы посмотрите на элемент <entity>, который определяет сущностный компонент Movie, вы увидите такую строку:

<abstract-schema-name>Movie</abstract-schema-name>

Этот элемент ассоциирует сущностный компонент Movie с именем, которое вы можете использовать в запросах EJB-QL (более-менее похоже на то, если бы компонент был SQL таблицей). По умолчанию EJBDoclet использует имя компонента в качестве абстрактного схемного имени, но вы можете указать другое имя, если хотите. Поля, которые вы можете запрашивать из компонента Movie, являются частью постоянных параметров, предоставляемых компонентом Movie (и параметров, которые являются частью связей, но мы рассмотрим это позже).

Посмотрите расположенное ниже в дескрипторе развертки определение компонента Movie , вы также увидите код, сгенерированный EJBDoclet'ом:

<query>
    <query-method>
        <method-name>findByTitle</method-name>
        <method-params>
            <method-param>java.lang.String</method-param>
        </method-params>
    </query-method>
    <ejb-ql>
        <![CDATA[SELECT OBJECT(m) FROM Movie m WHERE m.title=?1]]>
    </ejb-ql>
</query>

Здесь определяется запрос для поисковика findByTitle(…), сигнатуру которого мы определили в тэге ejb:finder, наряду с аргументами запроса и строкой запроса. Сама строка запроса вот:

SELECT OBJECT(m) FROM Movie m WHERE m.title=?1

Эта строка не сильно отличается от строки запроса SQL, и она означает, что вы хотите получить ("SELECT") все экземпляры компонентов ("OBJECT(m)") из абстрактной схемы типа "Movie", называемой "m" в данном запросе, при этом вы хотите только те экземпляры "m" ("WHERE"), свойство "title" которого равно "?1", что обозначает первый аргумент, передаваемый в поисковый метод (в данном случае это строка).

Другой поисковик в компоненте Movie, findAll( ), имеет очень похожий запрос, но без выражения WHERE, что означает, что вы хотите получить все экземпляры без какой-либо фильтрации.

Это почти все, что необходимо знать контейнеру для нахождения экземпляров Movie в соответствии с критериями, передаваемыми в запросе. Отсутствующая часть - это то, как отразить абстрактную схему данных на специфическую, конкретную базу данных. На этом спецификация останавливается, и это отображение оставляется на совесть поставщика контейнера. Другими словами, различные контейнеры предлагают разные пути и разные инструменты для отображения сущностных компонентов на базу данных - это работа Bean deployer'а. Многие контейнеры поставляются с некоторыми отображениями по умолчанию, к таким контейнерам относится и JBoss; поэтому мы можем не заботится, в этом примере, относительно отображения: мы используем базу данных по умолчанию и отображение по умолчанию, которые поставляются со стандартным пакетом установки JBoss. Если вы хотите что-то поднастроить по своим нуждам, вы должны обратиться к документации вашего контейнера.

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

Теперь, когда мы имеем пару дополнительных поисковиков, мы можем использовать их для реализации дополнительной функциональности в сессионном компоненте типа façade, особенно по причине того, что наш сущностный компонент более не доступен клиенту напрямую. Следующий далее код реализации сессионного компонента ShowListing имеет два дополнительных метода, которые клиенты могут использовать для получения фильмов по их заголовку или для получения всех фильмов:

//:example10/src/ejb-tier/javatheater/ejb/implementation/ShowListingBean.java
package javatheater.ejb.implementation;

import javatheater.ejb.*;

import javatheater.valueobject.MovieItem;

import javax.ejb.*;

import javax.naming.*;

import java.util.Collection;
import java.util.Iterator;

/**
* @ejb:bean type = "Stateless" name = "ShowListing" jndi-name =
*           "javatheater/ShowListing" reentrant = "false"
* @ejb:home remote-class="javatheater.ejb.ShowListingHome"
* @ejb:interface remote-class="javatheater.ejb.ShowListing"
*/
public abstract class ShowListingBean implements SessionBean {
  
MovieHome movieHome = null;
  
  
/**
    * @ejb:create-method
    */
  
public void ejbCreate() throws CreateException {
     
try {
        
movieHome = (MovieHome) new InitialContext()
              
.lookup("javatheater/Movie");
     
}
     
catch (NamingException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public MovieItem getMovie(Integer moviePK) {
     
try {
        
Movie movie = movieHome.findByPrimaryKey(moviePK);
        
return new MovieItem(movie.getId(), movie.getTitle());
     
}
     
catch (FinderException e) {
        
return null;
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public MovieItem[] getMovie(String title) {
     
try {
        
Collection foundMovies = movieHome.findByTitle(title);
         MovieItem
[] items = new MovieItem[foundMovies.size()];
        
int index = 0;
         Iterator movies = foundMovies.iterator
();
        
while (movies.hasNext()) {
           
Movie movie = (Movie) movies.next();
            items
[index++] = new MovieItem(movie.getId(), movie.getTitle());
        
}
        
return items;
     
}
     
catch (FinderException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public MovieItem[] getMovies() {
     
try {
        
Collection foundMovies = movieHome.findAll();
         MovieItem
[] items = new MovieItem[foundMovies.size()];
        
int index = 0;
         Iterator movies = foundMovies.iterator
();
        
while (movies.hasNext()) {
           
Movie movie = (Movie) movies.next();
            items
[index++] = new MovieItem(movie.getId(), movie.getTitle());
        
}
        
return items;
     
}
     
catch (FinderException e) {
        
throw new EJBException(e);
     
}
   }
}
// /:~

Есть еще много чего сказать о поисковиках и о EJB-QL (например, уровень доступа поисковиков или как выполнять более сложные запросы), но это только введение в EJB, мы не можем вдаваться во все детали. Однако вам было продемонстрирована сущность собственных поисковиков и EJB-QL.

В качестве последней ремарки укажу, что очень похожими на поисковики и EJB-QL являются методы выбора. Метод выбора очень похож на поисковый и он определяется аналогичным образом при использовании запроса. Однако есть два главных отличия между поисковым методом и методом выбора: 1) поисковик всегда возвращает ссылку на компонентный интерфейс сущностного компонента, в то время как метод выбора может возвращать любой тип постоянных свойств, имеющихся у компонента, и 2) методы выбора никогда не доступны клиенту, независимо от типа реализованного клиентского представления. Так, например, вы можете иметь метод выбора, который возвращает все названия фильмов (строки), в то время, как метод поиска должен возвращать фильмы. Методы выбора не предназначены для клиентов и могут быть использованы только для реализации части внутренней логики вашего сущностного компонента.