Entity Enterprise Java Beans

Obiekty encyjne

Typowy obiekt encyjny charakteryzuje się:

Typowy kontener i serwer zapewniają skalowalne środowisko dla wielu działających jednocześnie aktywnych obiektów encyjnych.

Klient lokalny i zdalny

Dla klienta entity bean to komponent reprezentujący w sposób obiektowy pewne dane (encje) zapisane w pamięci trwałej (np. w bazie danych) lub dane generowane przez działającą aplikację. Klient sięga do obiektu encyjnego przez interfejs domowy (home interface) i interfejs componentu (component interface). Kontener dostarcza klas, które implementują interfejs domowy i komponentu. Mimo, że klient korzysta z klas dostarczonych przez kontener, kontener sam w sobie jest dla niego przezroczysty. Klient może być zarówno zdalny jak i lokalny - zależnie czy używa lokalnego czy zdalnego interfejsu. Różnie między tymi klientami podane są poniżej.

Zdalny klient

Zdalny klient komunikuje się z obiektem encyjnym przez zdalny interfejs i używa zdalnego interfejsu domowego. Zdalny interfejs domowy dostarcza referencję do zdalnego interfejsu obiektu encyjnego. Zdalny interfejs nie zależy od lokalizaji obiektu. Klient działający na tej samej maszynie wirtualnej na której znajduje się instancja obiektu encyjnego używa tego samego API co klient działający na innej maszynie wirtualnej czy fizycznej. Zdalny interfejs bazuje na Java RMI (Java Remote Method Invocation) API, czyli zdalnym wywoływaniu metod. Obiekty te są więc dostępne przez standardowe wywoływanie metod, czego konsekwecją jest np. to, że argumenty i wyniki przekazywane są przez wartość. Zdalnym klientem może być zarówno ejb umieszczony w tym samym lub innym kontenerze jak i dowolny program w Javie, aplet lub servlet. Istnieje także możliwość mapowania na interfejs CORBY przez co z ejb mogą także korzystać klienci nie napisani w Javie.

Lokalny klient

Ejb mogą mieć również lokalnych klientów. Klient lokalny to klient umieszczony na tej samej maszynie wirtualnej co ziarenko ejb dostarczające interfejs lokalny. W odróżnieniu od interfejsu zdalnego, interfejs lokalny nie jest niezależny od lokalizacji. Komunikacja przezeń jest możliwa tylko w ramach jednej maszyny wirtualnej. Nie ma tu więc przezroczystości lokalizaji. Kontener dostarcza klasy implementujące ten interfejs. Obiekty tych klas są lokalnymi obiektami Javy. Argumenty i wyniki meted są więc przekazywane przez referencję i mogą być teoretycznie dzielone przez wywołującego i wywoływanego.

Entity EJB od strony klienta

Dla klienta entity ejb jest po prostu pewnym obiektem umożliwiającym reprezentację danych w sposób obiektowy. Nie wie on dokładnie w jaki sposób dane są przechowywane w pamięci trwałej. Od czasu stworzenia do momentu usunięcia ziarenko żyje w kontenerze. Przezroczyście dla klienta kontener zapewnia bezpieczeństwo, przetwarzanie równoległe, transakcyjność, trwałość. Kontener jest niezależny od klienta - nie istnieje API umożliwaiające klientowi manipulowanie kontenerem. Wielu klientów może mieć jednocześnie dostęp do jednego obiektu encyjnego. Kontener synchronizuje dostęp do obiektu encyjnego za pomocą transakcji. Każdy obiekt encyjny ma identyfikator, który zachowuje nawet po załamaniu się i restarcie kontenera. Identyfiaktor ten jest dostarczany przez kontener.

Kontener EJB

Kontener EJB (w skrócie kontener) to system stanowiący niejako "pojemnik czasu wykonania" dla ejb. W jednym pojemniku może być umieszczonych wiele obiektów encyjnych. Dla każdego z nich kontener dostarcza interfejs domowy (home interface), który umożliwia klientowi tworzyć, szukać i usuwać obiekty. Interfejs domowy może też dostarczać metod, które nie są specyficzne dla poszczególnego obiektu encyjnego. Kontener czyni interfejs domowy dostępnym w przestrzeni nazw JNDI (Java Naming and Directory Interface). Serwer EJB może zarządzać jednym lub wieloma kontenerami.

Lokalizowanie interfejsu domowego

Klient lokalizuje interfejs domowy przy pomocy JNDI. JNDI klienta może być tak skonfigurowany, aby widzić lokalizacje interfejsów domowych udostępnianych przez kontenery zlokalizowane na różnych maszynach w sieci. Aktualna lokalizacja kontenera EJB jest (w ogólności) przezroczysta dla klienta. Np. zdalny interfejs domowy ziarenka Account można zlokalizować przy pomocy następującego fragmentu kodu:

Context initialContext = new InitialContext();
AccountHome accountHome = (AccountHome)
   javax.rmi.PortableRemoteObject.narrow(
   initialContext.lookup("java:comp/env/ejb/accounts"),
   AccountHome.class);

Lokalizowanie lokalnego interfejsu domowego przy użyciu JNDI wygląda podobnie. Nie angażuje ono jednak API do zdalnego dostępu. Np. jeśli ziarenko Account ma interfejs domowy AccountHome, lokalny klient może zawierać następujący fragment kodu:

Context initialContext = new InitialContext();
AccountHome accountHome = (AccountHome)
   initialContext.lookup("java:comp/env/ejb/accounts");

Co zapewnia kontener

Następujący diagram obrazuje udostępnianie elementów przez kontener. Jak widać klient może być klientem lokalnym dla jednych EJB i zarówno zdalnym dla innych.

Zdalny interfejs domowy

Kontener dostarcza implementację zdalnego interfejsu domoweo dla każdego ziarenka w nim umieszczonego i definiującego ten interfejs. Obiekt implementujący interfejs domowy ziarenka nazywany jest obiektem EJBHome (EJBHome object).

Zdalny interfejs domowy ziarenka encyjnego pozwala klientowi na:

Zdalny interfejs domowy obiektu encyjnego musi dziedziczyć z intefrejsu javax.ejb.EJBHome i podlegać standardowym zasadom programowania zdalnych interfejsów w Javie.

Metody create

Interfejs domowy encyjnego ziarenka może definiować zero lub więcej metod create<METHOD>(...), jedną dla każdego sposobu tworzenia obiektu encyjnego. Argumenty tych metod służą zwykle inicjalizacji nowo tworzonego obiektu. Nazwa każdej tworzonej metody zaczyna się od prefiksu "create". Każda z metod create<METHOD> zwraca zdalny interfejs ziarenka. W przypadku niepowodzenia może zostać rzucony wyjątek java.rmi.RemoteException lub javax.ejb.CreateException. Następujący interfejs domowy deklaruje trzy metody tworzące:

public interface AccountHome extends javax.ejb.EJBHome {
  public Account create(String firstName, String lastName,
    double initialBalance)
    throws RemoteException, CreateException;
  public Account create(String accountNumber,
    double initialBalance)
    throws RemoteException, CreateException,
    LowInitialBalanceException;
  public Account createLargeAccount(String firstname,
    String lastname, double initialBalance)
    throws RemoteException, CreateException;
  ...
}

Następujący fragment kodu ilustruje jak klient tworzy nowy obiekt encyjny:

AccountHome accountHome = ...;
Account account = accountHome.create("John", "Smith", 500.00);

Metody find

Zdalny interfejs domowy obiektu encyjnego definiuje jedną lub więcej metod wyszukujących - jedną dla każdego sposobu wyszukiwania obiektu. Nazwa każdej takiej metody zaczyna się prefiksem "find", np. findLargeAccounts(...). Argumenty metody wyszukującej używane są przez implementację EJB do zlokalizowania szukanego obiektu. Tego typu metody zwracają wynik typu zdalnego interfejsu EJB lub kolekcji obiektów implementujących zdalny interfejs EJB.

Zbiór rzucanych wyjątków zawiera java.rmi.RemoteException oraz javax.ejb.FinderException.

Zdalny interfejs domowy zawiera metodę findByPrimaryKey(primaryKey), która pozwala klientowi zlokalizować obiekt encyjny używający danej wartości jako klucza głównego. Nazwa tej metody jest zawsze taka sama, ma ona pojedynczy argument typu klucza głównego i zwraca obiekt typu zdalnego interfejsu. Implementacja findByPrimaryKey(primaryKey) musi gwarantować, że odpowiedni obiekt encyjny istnieje.

Następujący przykład pokazuje implementację tej metody:

public interface AccountHome extends javax.ejb.EJBHome {
  ...
  public Account findByPrimaryKey(String AccountNumber)
    throws RemoteException, FinderException;
}

Oto jak klient wywołuje findByPrimaryKey:

AccountHome = ...;
Account account = accountHome.findByPrimaryKey("100-3450-3333");

Metody remove

Interfejs javax.ejb.EJBHome definjuje kilka metod, które umożliwiają klientowi usuwanie obiektów encyjnych.

public interface EJBHome extends Remote {
  void remove(Handle handle) throws RemoteException,
    RemoveException;
  void remove(Object primaryKey) throws RemoteException,
    RemoveException;
}

Po usunięciu obiektu encyjnego, kolejne do niego odwołania przez zdalnego klienta spowodują wyjątek java.rmi.NoSuchObjectException.

Metody home

Zdalny interfejs domowy może definiować jedną lub więcej metod dodatkowych (określanych mianem "home methods" - metod domowych). Są to metody realizujące logikę biznesową nie powiązaną z pojedynczą instancją obiektu encyjnego. Metody te mogą mieć dowolne nazwy z tym tylko zastrzerzeniem, że nie mogą one się zaczynać od "create", "find" ani "remove". Ich argumenty są zwykle wykorzystywane w implementacji w kontekście nie zależnym od żadnej konkretnej instancji ziarenka EJB. Argumenty metod domowych oraz zwracane przez nich wartości muszą być legalnymi typami dla RMI-IIOP.

W zbiorze rzucanych wyjątków znajduje się standardowo java.rmi.RemoteException

Oto prosty przykład dwóch metod:

public interface EmployeeHome extends javax.ejb.EJBHome {
  ...

  // this method returns a living index depending on
  // the state and the base salary of an employee:
  // the method is not specific to an instance
  public float livingIndex(String state, float Salary)
    throws RemoteException;

  // this method adds a bonus to all of the employees
  // based on a company profit-sharing index
  public void addBonus(float company_share_index)
    throws RemoteException, ShareIndexOutOfRangeException;

  ...
}

Lokalny interfejs domowy

Kontener dostarcza implementację lokalnego interfejsu domowego dla każdego ziarenka EJB umieszczonego w kontenerze definiującym lokalny interfejs domowy. Kontener czyni lokalny interfejs domowy dostępnym dla lokalnych klientów przez JNDI. Obiekt implementujący lokalny interfejs domowy nosi nazwę obiektu EJBLocalHome.

Lokalny interfejs domowy pozwala klientowi na:

Encyjny lokalny obiekt domowy musi dziedziczyć z interfejsu javax.ejb.EJBLocalHome.

Metody create

Encyjny interfejs domowy może definiować zero lub więcej metod create<METHOD>(...) . Nazwa każdej z nich zaczyna się od "create". Jako wynik metody tego typu zwracają lokalny interfejs obiektu encyjnego. Rzucane wyjątki zawierają javax.ejb.CreateException.

Przykład użycia metod create w lokalnym interfejsie domowym:

public interface AccountHome extends javax.ejb.EJBLocalHome {
  public Account create(String firstName, String lastName,
    double initialBalance)
    throws CreateException;
  public Account create(String accountNumber,
    double initialBalance)
    throws CreateException, LowInitialBalanceException;
  public Account createLargeAccount(String firstname,
    String lastname, double initialBalance)
    throws CreateException;
  ...
}

Przykład wywołania po stronie klienta:

AccountHome accountHome = ...;
Account account = accountHome.create("John", "Smith", 500.00);

Metody find

Lokalny interfejs domowy definiuje jedną lub więcej metod wyszukujących. Nazwa każdej z nich zaczyna się od "find" np. findLargeAccounts(...). Metody te zwracają lokalny interfejs ziarenka lub kolekcję obiektów implementujących ten interfejs. Wśród wyjątków mamy javax.ejb.FinderException. Nie można natomiast rzucać java.rmi.RemoteException. Lokalny interfejs domowy zawiera metodę findByPrimaryKey(primaryKey) (uwagi jak przy interfejsie zdalnym).

Przykład findByPrimaryKey

public interface AccountHome extends javax.ejb.EJBLocalHome {
  ...
  public Account findByPrimaryKey(String AccountNumber)
    throws FinderException;
}

Po stronie klienta:

AccountHome = ...;
Account account = accountHome.findByPrimaryKey("100-3450-3333");

Metody remove

Interfejs javax.ejb.EJBLocalHome definiuje metody pozwalające klientowi na usuwanie obiektów encyjnych.

public interface EJBLocalHome {
  void remove(Object primaryKey) throws RemoveException,
    EJBException;
}

Przy próbie odwołania do usuniętego obiektu zostanie rzucony wyjątek javax.ejb.NoSuchObjectLocalException.

Metody home

Lokalny interfejs domowy może definiować metody logiki biznesowej niezależne od jakiejś konkretnej instancji EJB. Ograniczenie: nazwy metod nie mogą zaczynać się od "create", "find" czy "remove". Nie mogą rzucać wyjątków java.rmi.RemoteException.

Np.

public interface EmployeeHome extends javax.ejb.EJBLocalHome {
  ...

  // this method returns a living index depending on
  // the state and the base salary of an employee:
  // the method is not specific to an instance
  public float livingIndex(String state, float Salary);

  // this method adds a bonus to all of the employees
  // based on a company profit sharing index
  public void addBonus(float company_share_index)
    throws ShareIndexOutOfRangeException;

  ...
}

Cykl życia obiektu encyjnego

Ta część opisuje cykl życia obiektu encyjnego. Następujący diagram ilustruje cykl życia obiektu z punku widzenia klienta (termin referenced na diagramie oznacza, że program klienta ma referencję do zdalnego lub lokalnego interfejsu obiektu encyjnego).

Obiekt encyjny nie istnieje dopóki nie zostanie stworzony. Dopóki nie zostanie stworzony, dopóty nie ma torzsamości. Przez tworzenie zyskuje torzsamość. Klient tworzy obiekt encyjny przy użyciu interfejsu domowego. Po stworzeniu obiektu encyjnego klient uzyskuje do niego referencję.

W środowisku z danymi dziedziczonymi, obiekty encyjne mogą "istnieć " zanim kontener i EJB zostaną zainicjalizowane. Na dodatek obiekt encyjny może zostać "stworzony" za pomocą mechanizmów innych niż wywołanie metody create<METHOD> (np. przez wstawienie rekordu do bazy) i wciąż może być dostępny przez metody wyszukujące. Obiekt encyjny może być bezpośrednio usunięty przy pomocy innych narzędzi niż wykonanie remove() (np. usunięcie rekordu z bazy). Przejścia "direct insert" oraz "direct delete" na diagramie odpowiadają bezpośrednim manipulacjom na bazie.

Wszystkie obiekty encyjne uznawane są za obiekty trwałe. Czas życia obiektu encyjnego nie jest ograniczony do czasu życia procesu wirtualnej maszyny Javy w której istnieje instancja EJB. Awaria wirtualnej maszyny Javy może spowodować wycofanie obecnej transakcji, nie powoduje natomiast zniszczenia wcześniej stworzonych obiektów ani nie unieważnia referencji do interfejsu domowego ani interfejsów klientów trzymanych przez klientów. Wielu klientów może pracować jednocześnie na tym samym obiekcie. Do izolacji używa się transakcji.

Klucz główny

Każdy obiekt encyjny ma unikatowy identyfikator w obrębie swego interfejsu domowego. Jeśli dwa obiekty mają ten sam interfejs domowy i taki sam klucz główny, są uważane za identyczne.

Architektura Enterprise JavaBeans pozwala, aby klasa klucza głównego była dowolną klasą będącą legalnym typem dla protokołu RMI-IIOP. Klasa klucz głównego może być zależna od klasy ziarenka (tzn. klasa EJB może zdefiniować jako swój klucz główny nową klasę lub użyć klucza głównego innej klasy EJB).

Klient może dostać identyfikator obiektu wywołując metodę getPrimaryKey() na referencji do obiektu.

Identyfikator obiektu nie ulega zmianie (tzn. getPrimaryKey() zawsze zwraca tą samą wartość jeśli jest wywoływana na tym samym obiekcie). Jeśli obiekt encyjny ma zarówno interfejs zdalny jak i lokalny to wywołanie getPrimaryKey() zwróci ten sam wynik.

Klient może sprawdzić czy dwie referencje do obiektów encyjnych odnoszą się do tego samego obiektu przez wywołanie metody isIdentical. Alternatywnie, jeśli obie te referencje zostały pozyskane przez ten sam interfejs domowy, można porównać ich klucze główne przy pomocy metody equals.

Następujący przykład ilustruje użycie metody isIdentical:

Account acc1 = ...;
Account acc2 = ...;

if (acc1.isIdentical(acc2)) {
  acc1 and acc2 are the same entity object
} else {
  acc2 and acc2 are different entity objects
}

Klient znając klucz główny obiektu encyjnego może dostać do niego referencję przez wywołanie findByPrimaryKey(key) na interfejsie domowym.

Architektura Enterprise JavaBeans nie określa "równości obiektów" (np. użycia operatora ==) dla referencji do obiektów encyjnych. Wynik metody Object.equals(Object obj) jest nieokreślony. Dlatego klient powinien zawsze używać isIdentical.

Zdalny interfejs Entity EJB

Klient może uzykać dostęp do obiektu encyjnego przez zdalny interfejs ziarenka. Interfejs ten musi dziedziczyć z interfejsu javax.ejb.EJBObject. Zdalny interfejs definiuje metody biznesowe, które mogą być wywoływane przez zdalnych klientów.

Poniższy przykład ilustruje definicję zdalnego interfejsu Entity EJB:

public interface Account extends javax.ejb.EJBObject {
  void debit(double amount)
    throws java.rmi.RemoteException,
    InsufficientBalanceException;
  void credit(double amount)
    throws java.rmi.RemoteException;
  double getBalance()
    throws java.rmi.RemoteException;
}

Interfejs javax.ejb.EJBObject definiuje metody, które pozwalają klientowi na następujące operacje na referencjach do obiektów:

Kontener dostarcza implementację metod zdefiniowanych w interfejsie javax.ejb.EJBObject. Tylko metody biznesowe muszą być zaimplementowane w klasie ziarenka.

Obiekt encyjny nie pozwala klientom na wywoływanie metod interfejsu javax.ejb.EnterpriseBean przez klienta. Są one używane tylko przez kontener do zarządzania instancjami ziarenek.

Lokalny interfejs Entity EJB

Lokalni klienci mogą uzyskać dostęp do obiektu encyjnego przez interfejs lokalny. Musi on dziedziczyć z interfejsu javax.ejb.EJBLocalObject. Lokalny interfejs definiuje metody biznesowe dostępne dla lokalnych klientów.

Poniższy przykład pokazuję jak może wyglądać lokalny interfejs:

public interface Account extends javax.ejb.EJBLocalObject {
  void debit(double amount)
    throws InsufficientBalanceException;
  void credit(double amount);
  double getBalance();
}

Interfejs javax.ejb.EJBLocalObject pozwala lokalnym klientom na wykonanie następujących operacji na referencji do lokalnego obiektu encyjnego:

Uchwyt do obiektu encyjnego

Uchwyt do obiektu encyjnego to obiekt identyfikujący obiekt encyjny w sieci. Klient posiadający referencję do zdalnego interfejsu obiektu encyjnego może uzyskać uchwyt przez wywołanie metody getHandle() na zdalnym interfejsie.

Ponieważ klasa uchwytu dziedziczy z java.io.Serializable, klient może serializować uchwyt i użyć go potem, być może w innym procesie lub nawet systemie, aby z powrotem uzyskać referencję do obiektu encyjnego identyfikowanego przez uchwyt.

Kod klienta musi użyć metody javax.rmi.PortableRemoteObject.narrow(...) aby przekształcić wynik metody getEJBObject() wywołanej na typie uchwytu.

Czas życia i zakres uchwytu zależy od jego implementacji. Program działający w jednej maszynie wirtualnej Javy powinien móc uzyskać i zserializować uchwyt, a inny program działający na innej maszynie deserializować uchwyt i odtworzyć referencję do obiektu. Uchwyt jest zwykle implementowany w ten sposób, że można go używać przez długi okres czasu - minimalny czas - restart serwera.

Kontener EJB nie musi akceptować uchwytu wygenerowanego przez kontener innego dostawcy.

Użycie uchwytu ilustruje następujący przykład:

// A client obtains a handle of an account entity object and
// stores the handle in stable storage.
//
ObjectOutputStream stream = ...;
Account account = ...;
Handle handle = account.getHandle();
stream.writeObject(handle);

// A client can read the handle from stable storage, and use the
// handle to resurrect an object reference to the
// account entity object.
//
ObjectInputStream stream = ...;
Handle handle = (Handle) stream.readObject(handle);
Account account = (Account)javax.rmi.PortableRemoteObject.narrow(
  handle.getEJBObject(), Account.class);
account.debit(100.00);

Uchwyt nie zawiera praw dostępu do obiektu. Po otrzymaniu z uchwytu referencji do obiektu, przy próbie wywołania metody na tym obiekcie, kontener dokonuje standardowej kontroli praw dostępu bazujących na prawach wywołującego.

Uchwyt do obiektu domowego

Specyfikacja EJB pozwala klientowi na uzyskanie uchwytu do zdalnego interfejsu domowego. Klient może zapamiętać ten uchwyt w pamięci stałej i odtworzyć później referencję. Funkcjonalność uchwytu może być użyteczna dla klienta, który potrzebuje użyć interfejsu zdalnego w przyszłości, ale nie zna jego nazwy JNDI.

Uchwyt do zdalnego interfejsu domowego musi implementować interfejs ejb.HomeHandle.

W kodzie klienta używa się metody javax.rmi.PortableRemoteObject.narrow(...) aby przekształcić wynik getEJBHome() - metody uchwytu na interfejs domowy.

Czas życia i zasięg - jak w uchwycie obiektu encyjnego.

Zawężanie typów referencyjnych

Program kliencki, który ma być przenośny między różnymi implementacjami kontenerów EJB musi używać javax.rmi.PortableRemoteObject.narrow(...) do zawężania typów zdalnego interfejsu i zdalnego interfejsu domowego

Programy, które używają operatora rzutowania w celu zawężania interfejsu zdalnego i zdalnego interfejsu domowego prawdopodobnie zawiodą jeśli implementacja kontenera używa RMI-IIOP do komunikacji.

Container-Managed Persistence

Architektura EJB wprowadza separację między tym jak ziarenko jest widziane przez klienta, a jego rzeczywistą implementacją. Wykorzystując zarządzanie trwałością przez kontener wprowadzamy kolejny poziom izolacji oddzielając metody biznesowe od metod zarządzających przechowywaniem danych (których nie musimy implementować). Dzięki temu możemy np. zmienić bazę danych nie ingerując w kod ziarenka.

Ograniczenia

Programując EJB z trwałością zarządzą przez kontener należy zwócić uwagę na następujące kwestie:

Relacje

Ziarenko EJB może być w relacji z innymi ziarenkami zarządzanymi przez kontener.

Relacje mogą być typu jeden do jeden, jeden do wiele lub wiele do wiele.

Relacje zarządzane przez kontener mogą istnieć tylko miedzy ziarenkami w ramach tego samego środowiska lokalnego definiowanego w sekcji dotyczącej relacji w deskryptorze wdrożeniowym.

Relacje mogą być dwukierunkowe lub jednokierunkowe. Jeśli relacja jest dwukierunkowa możliwe są przejścia między obiektami w obie strony. Jeśli jednokierunkowa - tylko w jedną stronę. Kierunek relacji określa się również w deskryptorze. Ziarenko, które nie ma interfejsu lokalnego może mieć tylko jednokierunkowe połączenia do innych ziarenek. Brak lokalnego interfejsu uniemożliwia włączanie do relacji przez inne ziarenka.

Programista EJB musi zdecydować o liczności relacji w momencie tworzenia ziarenka. Metody get dla pól cmr-field muszą zwracać albo lokalny interfejs, albo kolekcję (java.util.Collection lub java.util.Set) lokalnych interfejsow. Analogicznie dla parametrów metod set.

Usuwanie

Ziarenko może zostać usunięte na dwa sposoby:

W wyniku wywołania metody remove na obiekcie encyjnym, kontener musi wywołać ejbRemove(). Po zakończeniu ejbRemove() kontener musi usunąć obiekt encyjny ze wszystkich relacji do których należał, a potem usunąć jego trwałe dane. Po usunięci akcesory relacji jeden do jeden z tym obiektem zwrócą null; w przypadku relacji wiele do wiele wynikiem będzie kolekcja bez tego obiektu. Kontener musi wykrywać odwołania do usuniętego obiektu i rzucać wyjątek java.rmi.NoSuchObjectException (zdalny klient) lub javax.ejb.NoSuchObjectLocalException (lokalny klient).

Cascade-delete

Element cascade-delete deskryptora wdrożeniowego może być zawarty jest w elemencie ejb-relation-ship-role wewnątrz ejb-relation tylko jeśli drugi element ejb-relationship-role wewnątrz tego samego ejb-relation deklaruje liczność jeden.

Cascade-delete powoduje przy usuwaniu obiektu encyjnego usunięcie również obiektów połączonych z nim relacją.

Przykład

Poniższy przykład pokazuje jak może wygłądać kod ziarenka Entity EJB oraz deskryptor wdrożeniowy.

Kod ziarenka

package com.acme.order;

// This example shows the implementation of OrderBean, the
// entity bean class for the OrderEJB entity bean. OrderEJB has
// container-managed relationships with the entity beans
// CustomerEJB and LineItemEJB.
// This example illustrates the use of local interfaces.

import java.util.Collection;
import java.util.Vector;
import java.util.Date;
import javax.naming.*;

public abstract class OrderBean implements javax.ejb.EntityBean {
private javax.ejb.EntityContext context;

// define status codes for processing

static final int BACKORDER = 1;
static final int SHIPPED = 2;
static final int UNSHIPPED = 3;

// get and set methods for the cmp fields

public abstract int getOrderStatus();
public abstract void setOrderStatus(int orderStatus);
public abstract boolean getCreditApproved();
public abstract void setCreditApproved(boolean creditapproved);
public abstract Date getOrderDate();
public abstract void setOrderDate(Date orderDate);

// get and set methods for the relationship fields

public abstract Collection getLineItems();
public abstract void setLineItems(Collection lineitems);
public abstract Customer getCustomer();
public abstract void setCustomer(Customer customer);

// business methods.
// addLineItem:
// This method is used to add a line item.
// It creates the lineitem object and adds it to the
// persistent managed relationship.

public void addLineItem(Product product, int quantity, Address address)
  throws InsufficientInfoException
{

  // create a new line item

  if (validAddress(address)) {

    // Address is a legacy class. It is a dependent value
    // class that is available both in the client and in
    // the entity bean, and is serializable.
    // We will use the address as the value of a cmp field
    // of lineItem.

    try {
      Context ic = new InitialContext();
      LineItemLocalHome litemLocalHome =
        (LineItemLocalHome)ic.lookup("LineItemEJB");
      LineItem litem = litemLocalHome.create();
      litem.setProduct(product);
      litem.setQuantity(quantity);
      litem.setTax(calculateTax(product.getPrice(), quantity, address));
      litem.setStatus(UNSHIPPED);

      // set the address for the line item to be shipped

      litem.setAddress(address);

      // The lineItem entity bean uses a dependent value
      // class to represent the dates for the order status.
      // This class holds shipment date, expected shipment
      // date, credit approval date, and inventory
      // dates which are internal to the order fullfillment
      // process. Not all this information will be available
      // to the client.

      Dates dates = new Dates();
      litem.setDates(dates);
      getLineItems().add(litem);
    } catch (Exception someexception) {}
  } else {
    throw new InsufficientInfoException();
  }
}

// getOrderLineItems:
// This method makes a view of the lineitems that are in this
// order available in the client. It makes only the relevant
// information visible to the client and hides the internal
// details of the representation of the lineitem

public Collection getOrderLineItems() {
  Vector clientlineitems = new Vector();
  Collection lineitems = getLineItems();
  java.util.Iterator iterator = lineitems.iterator();

  // ClientLineItem is a value class that is used in
  // the client view.
  // The entity bean provider abstracts from the persistent
  // representation of the line item to construct the client
  // view.

  ClientLineItem clitem;
  while (iterator.hasNext()) {
    LineItem litem = (LineItem)iterator.next();
    clitem = new ClientLineItem();

    // only the name of the product is available in the
    // client view

    clitem.setProductName(litem.getProduct().getName());
    clitem.setQuantity(litem.getQuantity());

    // the client view gets a specific descriptive message
    // depending on the line item status.

    clitem.setCurrentStatus(
    statusCodeToString(litem.getStatus()));

    // address is not copied to the client view.
    // as this class includes other information with
    // respect to the order handing that should not be
    // available to the client. Only the relevant info
    // is copied.

    int lineitemStatus = litem.getStatus();
    if ( lineitemStatus == BACKORDER) {
      clitem.setShipDate(
      litem.getDates().getExpectedShipDate());
    } else
      if (lineitemStatus == SHIPPED) {
        clitem.setShipDate(
          litem.getDates().getShippedDate());
      }

    //add the new line item

    clientlineitems.add(clitem);
  }

  // return the value objects to the client

  return clientlineitems;
}

// other methods internal to the entity bean class
...
// other javax.ejb.EntityBean methods
...
}

Deskryptor wdrożeniowy

Deskryptor wdrożeniowy dostarcza następujących informacji o obiektach trwałych oraz relacjach:

<ejb-jar>
  ...
  <enterprise-beans>
    ...
  </enterprise-beans>
  <relationships>
    <!--
      ONE-TO-MANY: Order LineItem
    -->
    <ejb-relation>
      <ejb-relation-name>Order-LineItem</ejb-relation-name>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          order-has-lineitems
        </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
        </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>
    <!--
      ONE-TO-MANY unidirectional relationship:
      Product is not aware of its relationship with LineItem
    -->
    <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
        -->
      </ejb-relationship-role>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          lineitem-for-product
        </ejb-relationship-role-name>
        <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>
    <!--
      ONE-TO-MANY: Order Customer:
    -->
    <ejb-relation>
      <ejb-relation-name>Order-Customer</ejb-relation-name>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          customer-has-orders
        </ejb-relationship-role-name>
        <multiplicity>One</multiplicity>
        <relationship-role-source>
          <ejb-name>CustomerEJB</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>orders</cmr-field-name>
          <cmr-field-type>java.util.Collection
          </cmr-field-type>
        </cmr-field>
      </ejb-relationship-role>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          order-belongsto-customer
        </ejb-relationship-role-name>
        <multiplicity>Many</multiplicity>
        <relationship-role-source>
          <ejb-name>OrderEJB</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>customer</cmr-field-name>
        </cmr-field>
      </ejb-relationship-role>
    </ejb-relation>
  </relationships>
  ...
</ejb-jar>

Cykl życia instancji ziarenka


Instancja ziarenka może być w jednym z następujących stanów:

Programista EJB odpowiada za implementację następujących metod w abstrakcyjnej klasie ziarenka:

EJB QL - EJB Query Language

EJB QL to język zapytań przeznaczony dla ziarenek z trwałością zarządzaną przez kontener. EJB QL może zostać skompilowany do docelowego języka zapytań bazy danych takiego jak SQL. Umożliwia szeroko pojętą przenośność oraz optymalizację zapytań podczas tłumaczenia na język docelowy (a nie w czasie wykonania).

Zapytania EJB QL mogą być wykorzystywane na dwa różne sposoby:

Zapytanie EJB QL to napis składający się z trzech następujących części:

W zapytaniach EJB QL możemy się odnosić do trwałych pól innych ziarenek. Odnosimy się do nich przez abstakcyjne nazwy (element <abstract-schema-name>).

Przykłady

Przyjmujemy następujące konwencje nazewnicze: Ziarenko encyjne jako całość ma nazwę <name>EJB, a jego klasa i abstrakcyjna nazwa to <name>. W pierwszym przykładzie zakładamy, że programista implementuje ziarenka OrderEJB, Pro-ductEJB, LineItemEJB, ShippingAddressEJB oraz BillingAddressEJB. Są one zawarte w tym samym pliku ejb-jar. Tylko dwa ziarenka: OrderEJB oraz ProductEJB mają interfejs zdalny. Zależności pokazano na poniższym rysunku.

Zapytanie EJB QL znajdujące wszystkie zamówienia z podległymi pozycjami może być zapisane następująco:

SELECT DISTINCT OBJECT(o)
FROM Order AS o, IN(o.lineItems) AS l
WHERE l.shipped = FALSE

Zapytanie znajdujące zamównienia dla produktów typu office supplies:

SELECT DISTINCT OBJECT(o)
FROM Order o, IN(o.lineItems) l
WHERE l.product.product_type = ‘office_supplies’

Znajdź zamówienia zawierające jakieś produkty:

SELECT DISTINCT OBJECT(o)
FROM Order o, IN(o.lineItems) l

lub

SELECT OBJECT(o)
FROM Order o
WHERE o.lineItems IS NOT EMPTY

Zapytania dla metod select

Następujące zapytania zwracają wartości inne niż ziarenka ejb.

Następujące zapytania EJB QL wybiera nazwy wszystkich zamówionych produktów.

SELECT DISTINCT l.product.name
FROM Order o, IN(o.lineItems) l

Następujące zapytanie odnajduje nazwy produktów w zamówieniu o określonym numerze. Numer zamówienia podany jest do zapytania jako parametr.

SELECT l.product.name
FROM Order o, IN(o.lineItems) l
WHERE o.ordernumber = ?1

Bean-Managed Persistence

W przypadku ziarenek z trwałością zarządzaną przez ziarenko, programista ma możliwość ręcznie zaimplementować metody odpowiadające za utrwalanie i odczytywanie trwałych danych. Używając trwałości zarządzanej przez kontener, programista łączy się z bazą (np. przez JDBC lub SQLJ) bezpośrednio w kodzie ziarenka. Zapytania do bazy umieszczane są w metodach ejbCreate<METHOD>(...), ejbRemove(), ejbFind<METHOD>(...), ejbLoad() i ejbStore() oraz/lub w metodach biznesowych.

W przeciwieństwie do trwałości zarządzanej przez kontener, programista nie opisuje relacji w deskryptorze wdrożeniowym. Musi sam zadbać o zachowanie odpowiednich zależności wewnątrz kodu ziarenka.

Cykl życia instancji


Instancja ziarenka może być w jednym z następujących stanów:

Programista EJB odpowiada za implementację następujących metod w abstrakcyjnej klasie ziarenka (opisuję tylko metody różniące się od odpowiednich metod w ziarenku z trwałością zarządzaną przez kontener):