Przemysław Ciok pc181036@zodiac.mimuw.edu.pl

EJB-QL
Entity JavaBeans Query Language

1. Wprowadzenie

Specyfikacja EJB 1.1 nie zawierała standardu określającego sposób definiowania metod-zapytań dla ziaren EJB z właściwością CMP (container-managed persistence). Nie można było więc osiągnąć przenośności w odpowiednio dużym stopniu, gdyż za każdym razem przy przenoszeniu aplikacji z jednej architektury na drugą konieczne było przedefiniowywanie odpowiednich metod odpowiadających wyszukiwaniu obiektów na podstawie zapytań SZBD.

Specyfikacja ta nie zawierała także żadnego standardu określającego możliwość definiowania związków, relacji i asocjacji pomiędzy różnymi ziarnami. Brakowało więc możliwości przechodzenia w zapytaniach związanych z pewną klasą do zależnych z nią klas oraz ich pól. Jednak już w wersji 2.0 specyfikacji EJB (02.06.2001) został zdefiniowany język zapytań EJB-QL. Co ciekawe, jest to język bazujący na SQL-92 z dobrze wszystkim znanymi klauzulami SELECT, WHERE oraz FROM. Istotną częścią specyfikacji tego języka jest jednak standard definiowania relacji między klasami poprzez wprowadzenie abstrakcyjnych typów schematów oraz definicji zapytań w deskryptorach wdrożeniowych.

2. Zapytania EJB-QL i metody im odpowiadające

Zapytania EJB-QL muszą zawierać klauzule FROM oraz SELECT. Ta druga była na początku rozważana jako opcjonalna (na początku prac nad EJB 2.0), ale ostatecznie jest konieczna. Opcjonalna jest natomiast klauzula WHERE.
W jaki sposób zapytanie jest definiowane w deskryptorze wdrożeniowym?
<query>
	<query-method>
		<method-name></method-name>
		<method-params> 
			<method-param></method-param>
		</method-params>	
	</query-method>
	<result-type-mapping></result-type-mapping>
	<ejb-ql></ejb-ql>
</query>

Nazwa metody to <method-name>, natomiast jej parametry to <method-params>.
<result-type-mapping> określa zwracany typ i może zawierać wartości lokalne (Local), bądź zdalne (Remote). Tag <ejb-ql> zawiera natomiast treść samego zapytania.

Oto przykładowy model:

Zamówienie (Order) posiada adres (Address) i składa się z wielu pozycji(LineItem), każda z nich dotyczy jednego produktu (Product). Relacje w deskryptorze wdrożeniowym są zadeklarowane w następujący sposób:

<relationships>
<!--
JEDEN-DO-WIELE: Order LineItem   pozycje wchodzące w skład zamówienia
-->

<ejb-relation>
  <ejb-relation-name>Order-LineItem</ejb-relation-name>
  <ejb-relationship-role>
    <ejb-relationship-role-name>
    order-has-lineitems       zamówienie posiada pozycje
    </ejb-relationship-role-name>
    <multiplicity>One</multiplicity>
    <relationship-role-source>
      <ejb-name>OrderEJB</ejb-name>
    </relationship-role-source>
    <cmr-field>
      <cmr-field-name>lineItems</cmr-field-name>
      <cmr-field-type>java.util.Collection
      </cmr-field-type>
    </cmr-field>
  </ejb-relationship-role>

  <ejb-relationship-role>
    <ejb-relationship-role-name>lineitem-belongsto-order   pozycja dotyczy konkretnego zamówienia
    </ejb-relationship-role-name>
    <multiplicity>Many</multiplicity>
    <cascade-delete/>
    <relationship-role-source>
      <ejb-name>LineItemEJB</ejb-name>
    </relationship-role-source>
    <cmr-field>
      <cmr-field-name>order</cmr-field-name>
    </cmr-field>
  </ejb-relationship-role>
</ejb-relation>

<!--
JEDEN-DO-WIELE relacja jednostronna:
Product nie jest świadomy swojego powiązania z LineItem     zależność pozycji z produktem
-->
<ejb-relation>
  <ejb-relation-name>Product-LineItem</ejb-relation-name>
  <ejb-relationship-role>
    <ejb-relationship-role-name>product-has-lineitems</ejb-relationship-role-name>
    <multiplicity>One</multiplicity>
    <relationship-role-source>
      <ejb-name>ProductEJB</ejb-name>
    </relationship-role-source>
    <!-- since Product does not know about LineItem there is no cmr field in Product for accessing Lineitem -->
        nie ma taga cmr-field bo produkt nie wie o pozycji która mu odpowiada
  </ejb-relationship-role>
  <ejb-relationship-role>
    <ejb-relationship-role-name>lineitem-for-product</ejb-relationship-role-name>    pozycja dotyczy produktu
    <multiplicity>Many</multiplicity>
    <relationship-role-source>
      <ejb-name>LineItemEJB</ejb-name>
    </relationship-role-source>
    <cmr-field>
      <cmr-field-name>product</cmr-field-name>
    </cmr-field>
  </ejb-relationship-role>
</ejb-relation>
</relationships>

(pominięta definicja relacji Address <-> Order).


Metody dzielą się na:

metody wyszukujące

Metody wyszukujące ( finder methods ) zwracają kolekcje, bądź pojedyncze instancje utrwalonych ziaren EJB (de facto w relacyjnej bazy danych). Te metody definiują interfejs domowy ziarna encyjnego, tak więc są widoczne na zewnątrz. Może to być:
  • interfejs zdalny (EJBHome)

  • zwracający typ odpowiadający zdalnemu interfejsowi bądź też kolekcji obiektów go implementujących

    public interface OrderHome extends javax.ejb.EJBHome {
        ...
      public Order findByPrimaryKey(int orderId) throws FinderException,RemoteException; zwraca pojedynczy obiekt
      public Order findByBiggestOrder() throws FinderException,RemoteException;
      public java.util.Collection findAllOrders(String supplierName)           zwraca kolekcję
        throws FinderException,RemoteException;
    }
  • bądź też lokalny (EJBLocalHome).

  • zwracający typ odpowiadający lokalnemu interfejsowi bądź też kolekcji obiektów go implementujących

    public interface OrderHome extends javax.ejb.EJBLocalHome {
        ...
      public Order findByPrimaryKey(int orderId) throws FinderException;     zwraca pojedynczy obiekt
      public Order findBiggestOrder() throws FinderException;
      public java.util.Collection findAllOrders(String supplierName)           zwraca kolekcję
        throws FinderException;
    }

    metody selekcjonujące

    Metody selekcjonujące ( select methods ) istnieją w ziarnach EJB w postaci metod odpowiadających zapytaniom. Nie są widoczne na zewnątrz. Zwracają instancje typów trwałych pól (cmp-fields), bądź też zdalne interfejsy związane z obiektem danego typu - zdefiniowane przez relacje w cmr-fields.
    Zasadniczo metody selekcjonujące dzielą się na dwa typy meotd:

    Przykład:

    public abstract class OrderBean implements javax.ejb.EntityBean {
    ...
    public abstract java.util.Collection ejbSelectAllOrderedProducts(Date date)       metoda ogólna
    throws FinderException;
    ...
    public abstract java.util.Collection ejbSelectAllOrderedProductsInEntity(Date date) metoda związana z konkretną instancją
    throws FinderException;
    }

    Metody postaci ejbSelect<METHOD> nie są związane z konkrtną instacją ziarna encyjnego. W powyższym przykładzie ejbSelectAllOrderProducts zwraca kolekcję wszystkich produktów (Product) związanych z wszystkimi zamówieniami (Order). Natomiast metody postaci ejbSelect<METHOD>InEntity są związane z konretną instancją obiektu encyjnego. Powyższa metoda ejbSelectAllOrderProductsInEntity zwraca wszystkie produkty, ale związane tylko z tą instancją OrderBean w której została wywołana.

    Tutaj nie trzeba zwracać java.util.Collection, chodzi o klasę reprezentującą zbiór elementów, może to być także java.util.Set eliminujący powtarzające się elementy itp.

    3. Gramatyka EJB-QL

    Oto gramatyka zapisana w notacji BNF.
    EJB QL ::= select_clause from_clause [where_clause]
    
    from_clause ::= FROM identification_variable_declaration 
        [, identification_variable_declaration]*
    
    identification_variable_declaration ::=
        collection_member_declaration |
        range_variable_declaration
    
    collection_member_declaration ::= 
        IN (collection_valued_path_expression) [AS] identifier
     
    range_variable_declaration ::= 
        abstract_schema_name [AS] identifier 
    
    single_valued_path_expression ::=
        {single_valued_navigation |
        identification_variable}.cmp_field |
        single_valued_navigation
    
    single_valued_navigation ::= 
        identification_variable.[single_valued_cmr_field.]*
        single_valued_cmr_field
    
    collection_valued_path_expression ::= 
        identification_variable.[single_valued_cmr_field.]*
        collection_valued_cmr_field
    
    select_clause ::= SELECT [DISTINCT]
        {single_valued_path_expression |
        OBJECT(identification_variable)}
    
    where_clause ::= WHERE conditional_expression
    
    conditional_expression ::= conditional_term |
        conditional_expression OR conditional_term
    
    conditional_term ::= conditional_factor | 
        conditional_term AND conditional_factor
    
    conditional_factor ::= [ NOT ] conditional_test
    
    conditional_test ::= conditional_primary 
    
    conditional_primary ::= 
        simple_cond_expression | (conditional_expression)
    
    simple_cond_expression ::= 
        comparison_expression | 
        between_expression | 
        like_expression |
        in_expression | 
        null_comparison_expression |
        empty_collection_comparison_expression |
        collection_member_expression
    
    
    between_expression ::= 
        arithmetic_expression [NOT] BETWEEN 
        arithmetic_expression AND arithmetic_expression 
    
    in_expression ::= 
        single_valued_path_expression 
        [NOT] IN (string_literal [, string_literal]* ) 
    
    like_expression ::= 
        single_valued_path_expression 
        [NOT] LIKE pattern_value [ESCAPE escape-character] 
    
    null_comparison_expression ::= 
        single_valued_path_expression IS [NOT] NULL
    
    empty_collection_comparison_expression ::= 
        collection_valued_path_expression IS [NOT] EMPTY
    
    collection_member_expression ::=
        {single_valued_navigation | identification_variable |
        input_parameter}
        [NOT] MEMBER [OF] collection_valued_path_expression
    
    comparison_expression ::= 
        string_value { =|<>} string_expression | 
        boolean_value { =|<>} boolean_expression} | 
        datetime_value { = | <> | > | < } datetime_expression | 
        entity_bean_value { = | <> } entity_bean_expression | 
        arithmetic_value comparison_operator
        single_value_designator
    
    arithmetic_value ::= single_valued_path_expression |
        functions_returning_numerics
    
    single_value_designator ::= scalar_expression
    
    comparison_operator ::= 
        = | > | >= | < | <= | <>
    
    scalar_expression ::= arithmetic_expression 
    
    arithmetic_expression ::= arithmetic_term |
        arithmetic_expression { + | - } arithmetic_term
    
    arithmetic_term ::= arithmetic_factor | 
        arithmetic_term { * | / } arithmetic_factor
    
    arithmetic_factor ::= { + |- } arithmetic_primary
    
    arithmetic_primary ::= single_valued_path_expression | 
        literal | (arithmetic_expression) |
        input_parameter | functions_returning_numerics
    
    string_value ::= single_valued_path_expression |
        functions_returning_strings
    
    string_expression ::= string_primary | input_expression
    
    string_primary ::= single_valued_path_expression | literal |
        (string_expression) | functions_returning_strings 
    
    datetime_value ::= single_valued_path_expression
    
    datetime_expression ::= datetime_value | input_parameter
    
    boolean_value ::= single_valued_path_expression
    
    boolean_expression ::= single_valued_path_expression | 
       literal | input_parameter
    
    entity_bean_value ::= 
        single_valued_navigation | identification_variable
    
    entity_bean_expression ::= entity_bean_value | input_parameter
    
    functions_returning_strings ::= 
        CONCAT(string_expression, string_expression) | 
        SUBSTRING(string_expression, arithmetic_expression,
        arithmetic_expression)
    
    functions_returning_numerics::= 
        LENGTH(string_expression) |
        LOCATE(string_expression, 
        string_expression[, arithmetic_expression]) |
        ABS(arithmetic_expression) |
        SQRT(arithmetic_expression) 
     
    

    4. Składnia zapytań.

    klauzula FROM

    from_clause ::= FROM identification_variable_declaration [, identification_variable_declaration]*identification_variable_declaration ::= collection_member_declaration | range_variable_declarationcollection_member_declaration ::= IN (collection_valued_path_expression) [AS] identifier range_variable_declaration ::= abstract_schema_name [AS] identifier

    Identyfikatory

    Identyfikatory to ciągi znaków będące poprawnymi identyfiaktorami w Javie, nie zawierające znaków zapytania. Wielkość liter nie ma znaczenia. Dodatkowo, nie mogą być zarezerwowanymi słowami kluczowymi EJB-QL:
    AND
    AS
    BETWEEN
    DISTINCT
    EMPTY
    FALSE
    FROM
    IN
    IS
    LIKE
    MEMBER
    NOT
    NULL
    OBJECT
    OF
    OR
    SELECT
    TRUE
    UNKNOWN
    WHERE
    Jak widać są to także słowa kluczowe SQL, ale nie wszystkie. W przyszłości również pozostałe zarezerwowane indentyfikatory z SQL mogą być dodane do specyfikacji EJB-QL, tak więc zalecane jest nieużywanie ich jako nazw identyfikatorów.

    Zmienne identyfikujące

    Są to identyfikatory zadeklarowane w klauzuli FROM, które mogą być oczywiście używane także przez klauzule WHERE oraz SELECT. Ponieważ te zmienne są identyfikatorami, obejmują je restrykcje ich dotyczące. Dodatkowo nie mogą być identyczne z nazwami ziaren encyjnych EJB oraz zadeklarowanych abstrakcyjnych schematów.
    Przykładowo w zapytaniu:

    SELECT OBJECT(o) FROM Order o, IN (o.lineItems) li
    WHERE li.product.product_type='Floppy Drive'

    zdefiniowane są zmienne: o, oraz li. o to zmienna odpowiadająca zamówienion, a li oznacza kolekcję lineItems dotyczącą pozycji zamówienia o. Zmienna o odpowiada abstakcyjnemu typowi ze schematu, podobnie li oraz li.product - tak będzie można dostać się do produktu odpowiadającego pozycji zamówienia.

    Identyfikator OBJCECT w klauzuli SELECT określa że o jest obiektem, a nie na przykład klasą java.lang.String - która byłaby zwracana gdyby klauzula ta miała postać:

    SELECT li.product.product_type ...

    Wyrażenia ścieżkowe mają postać:
    single_valued_path_expression ::=
       {single_valued_navigation |
       identification_variable}.cmp_field |
       single_valued_navigation
    
    single_valued_navigation ::= 
       identification_variable.[single_valued_cmr_field.]*
       single_valued_cmr_field
    
    collection_valued_path_expression ::= 
       identification_variable.[single_valued_cmr_field.]*
       collection_valued_cmr_field
    
    cmp-field odpowiadają utrwalanym polom ziaren EJB, a cmr-field odpowiadają relacjom zdefiniowanym w deskryptorze wdrożeniowym. Wyrażenia ścieżkowe w łatwy sposób umożliwiają przechodzenie po powiązanych ze sobą ziarnach. Wyrażenia mogą być więc trzech typów co odpowiada zwracanym przez odpowiednie metody typom standardowym (pola obiektu), pojedynczym obiektom i kolekcjom obiektów.

    klauzula WHERE

    where_clause ::= WHERE conditional_expression
    
    Klauzula WHERE określa które elementy powinny być brane pod uwagę na podstawie pewnych własności ich pól. Wyrażenie warunkowe może także zawierać spójniki logiczne.
    conditional_expression ::= conditional_term |
       conditional_expression OR conditional_term
    
    conditional_term ::= conditional_factor | 
       conditional_term AND conditional_factor
    
    conditional_factor ::= [ NOT ] conditional_test
    
    conditional_test ::= conditional_primary 
    
    conditional_primary ::= 
       simple_cond_expression | (conditional_expression)
    
    simple_cond_expression ::= 
       comparison_expression | 
       between_expression | 
       like_expression |
       in_expression | 
       null_comparison_expression |
       empty_collection_comparison_expression |
       collection_member_expression
    
    Mogą występować konstrukcje dobrze znane ze składni SQL: BETWEEN, IN, LIKE, porównywanie do NULL. A także sprawdzanie czy kolekcja jest pusta (EMPTY) bądź też - czy zawiera dany element (MEMBER OF). Wartośći typu NULL powstają jeżeli referencja nie wskazuje obiektu utrwalonego w bazie. Semantyka tej wartości jest taka sama jak zwykłego NULL w SQL. Podobnie jest, jeśli chodzi o porównywanie wartości określonych i nieokreślonych. Priorytety operatorów są oczywiście również takie same jak w SQL:
    Typ
    Porządek
    Nawigacyjny
    . (kropka w wyrażeniu ścieżkowym)
    Arytmetyczny
    + - (unarne)
    * / (mnożenie i dzielenie)
    + - (dodawanie i odejmowanie)
    Porównywanie
    =
    >
    >=
    <
    <=
    <> (różne od)
    Logiczne
    NOT
    AND
    OR

    Przykładowe zapytanie (zapisane na dwa sposoby): Wybierz wszystkie pozycje których zamówiona ilość jest mniejsza od 100, albo większa do 200:

    SELECT  OBJECT (li) FROM lineItems AS li
    WHERE li.quantity NOT BETWEEN 100 and 200
    
    SELECT  OBJECT (li) FROM lineItems AS li
    WHERE li.quantity < 100 AND li.quantity > 200

    Literały

    Używane w wyrażeniach warunkowych literały mogą mieć postać napisów (np. 'napis'), liczb (w zależności od użytego formatu może to być long bądź double z Javy) oraz logicznych stałych TRUE i FALSE.

    Parametry wejściowe

    Jako że zapytaniom odpowiadają metody którym przekazywane są parametry, możliwe jest odwoływanie się do nich.

    public abstract class OrderBean implements javax.ejb.EntityBean {
    ...
    //method-a
    public abstract java.util.Collection ejbSelectLineItems(int quantity)     parametr przekazywany
    throws FinderException;
    ...

    Oto zapytanie odpowiadające tej metodzie:

    SELECT OBJECT (o) FROM Order AS o IN (o.lineItems) li
    WHERE li.quantity = ?1

    gdzie ?1 określa pierwszy parametr. Definicja w deskryptorze wdrożeniowym będzie następująca:

    <query>
      <description>
    Method to find order specified no of lineItems</description>
      <query-method>
        <method-name>ejbSelectLineItems</method-name>
        <method-params>
          <method-param>int</method-param>
        </method-params>  
      </query-method>
    <result-type-mapping>Local</result-type-mapping>
      <ejb-ql>
    SELECT OBJECT (o) FROM Order AS o IN (o.lineItems) li
    WHERE li.quantity = ?1
    </ejb-ql>
    </query>


    Inny przykład metody i wykorzystania jej parametrów:

    //method-b
    public abstract java.util.Collection ejbSelectAllProducts(String product_type, double price) parametry przekazywane
    throws FinderException;
    }

    Oto zapytanie odpowiadające tej metodzie:

    SELECT OBJECT (o) FROM Order AS o IN(o.lineItems) li
    WHERE li.product.product_type=?1 AND li.product.price=?2

    Wbudowane wyrażenia funkcyjne

    EJB QL zawiera kilka wbudowanych operacji na obiektach klasy String i innych prostych typach, na przykład

    Funkcje obsługujące String:

    Funkcje arytmetyczne:

    Klauzula SELECT

    select_clause ::= SELECT [DISTINCT]
       {single_valued_path_expression |
       OBJECT(identification_variable)}
    
    Oczywiście typ zwracanej wartości, jak to już było wspomniane, musi odpowiadać typowi odpowiedniej metody wyszukującej lub selekcjonującej.
    Dla metod wyszukujących musi to być obiekt relaizujący odpowiedni interfejs, bądź też ich kolejkcja, na przykład:
    public Collection findAll() throws FinderException;
    

    Zapytanie findall zwraca kolekcję obiektów relizująch interejs zamówien (LocalOrder) :

    SELECT OBJECT(o) 
    FROM Order o
    

    Dla metod selekcjonujących, typ zwracany może być zgodny z : Klauzula SELECT NIE MOŻE zwracać wartości będącej kolekcją gdyż z jej specyfiki wynika to że zwraca wszystkie obiekty zgodne z klauzulami FROM i WHERE.
    SELECT o.lineItems FROM Order As o             tak jest źle
    SELECT OBJECT(li) FROM Order As o, IN(o.lineItems) li     tak jest dobrze.

    W tym drugim przypadku, wybierane są poszczególne obiekty z kolekcji, a nie całe kolekcje.

    DISTINCT

    Słowo kluczowe DISTINCT eliminuje powtarzające się elementy. Jednak java.util.Set sam z siebie je eliminuje, dlatego zasadne jest używanie DISTINCT tylko w przypadku metod zwracających np. java.util.Collection.

    OBJECT

    Słowo kluczowe OBJECT poprzedza pojedyncze wystąpienia zmiennych, to znaczy nie będące wyrażeniami ścieżkowymi.

    5. Wady i Zalety

    Zalety:
  • W końcu ktoś ustandaryzował mechanizm zapytań, co zapewnia przenośność bez względu na platformę
  • Kontener EJB parsuje i sprawdza poprawność zapytań przed załadowaniem Entity Beans, ponieważ EJB-QL używa abstrakcyjnych schematów danych oraz relacji zdefiniowanych poprzez cmr-fields - a one są zdefiniowane w deskryptorze wdrożeniowym.

    Wady:
  • Nie ma odpowiednika użytecznej dla dużych kolekcji klauzuli ORDER BY
  • Oraz jeszcze kilku SQL'owych konstrukcji.
  • Daty muszą być przekazywane jako long
  • Problemy z porównywaniem liczb rzeczywistych
  • Porównywanie string'ów i wartości logicznych ograniczone do = i <>
  • Nie można używać komentarzy w kodzie
  • 6. Podsumowanie

    EJB-QL w wersji 2.0 zawiera już wystarczająco dużo elementów, aby tworzyć odpowiednio funkcjonalne, przenośne aplikacje. Jednak nie zawiera jeszcze wielu mniej istotnych elementów. Możliwe że kolejne wersje uzupełnią ten język o wiele znanych z SQL konstrukcji.

    Przypuszczalnie jednak zostaną poczynione kroki aby oderwać się od relacyjnego schematu baz danych.

    7. Linki

    kilka adresów pod którymi można szukać informacji o EJB-QL:
    J2EE tutorialTutorial J2EE: EJBQL
    Learning EJBQLO'Reilly Network: learning EJB-QL
    EJB-QL WorldEJB-QL World The place for Enterprise JavaBeans answers
    Differences between EJB1.1 and EJB2.0Differences between EJB1.1 and EJB2.0: EJB-QL