132 Encyjne komponenty EJB Marek Wojciechowski Marek.Wojciechowski@cs.put.poznan.pl http://www.cs.put.poznan.pl/mwojciechowski/
Plan rozdziału 133 Wprowadzenie do encyjnych komponentów EJB Cykl życia encyjnego EJB Tworzenie encyjnego EJB w środowisku Oracle JDeveloper Wykorzystywanie encyjnych EJB Powiązania między encyjnymi EJB Transakcje zarządzane przez kontener Dostęp do encyjnych EJB z poziomu JSP
Encyjne komponenty EJB 134 Reprezentują obiekty składowane w bazie danych Na platformie J2EE podstawowym mechanizmem trwałego przechowywania danych jest relacyjna baza danych Przykłady trwałych obiektów: pracownicy, oddziały, produkty,... Nie modelują procesów, nie zawierają skomplikowanej logiki biznesowej, ale zarządzają danymi wykorzystywanymi w procesach biznesowych Mogą zawierać kod np. walidujący dane Typowo każdy komponent encyjny jest oparty o tabelę w relacyjnej bazie danych, a każda instancja komponentu encyjnego reprezentuje wiersz w tabeli Dane z tabel przedstawiane w formie obiektów
Instancje encyjnych EJB a dane w bazie danych 135 Wiele instancji encyjnego komponentu EJB może w danej chwili reprezentować te same dane (ten sam wiersz tabeli) Synchronizacja instancji komponentu z bazą danych jest obsługiwana przez kontener Dana instancja encyjnego komponentu EJB nie musi przez cały czas swego istnienia reprezentować tych samych danych (!) Kontener utrzymuje pulę (pool) instancji komponentu W czasie swego istnienia instancje są aktywowane (pobierane z puli) i pasywowane (zwracane do puli) Aktywowane instancje są dynamicznie przypisywane do reprezentacji instancji danych z bazy danych (w kolejnych "okresach aktywności" instancja komponentu może reprezentować różne obiekty tego samego typu)
Cechy encyjnych komponentów EJB 136 Trwałość Składowane w bazie danych (typowo) Odporne na awarie aplikacji, serwera aplikacji, serwera bd Trwałość zarządzana przez kontener (CMP) lub komponent (BMP) Współdzielenie komponentu przez wielu klientów Działanie w ramach transakcji (zarządzanych przez kontener: CMT) Możliwość współdzielenia instancji komponentu przez kilku klientów lub korzystania z różnych instancji reprezentujących te same dane Klucz główny (primary key) Unikalny identyfikator obiektu Podstawowe kryterium wyszukiwania obiektów Związki Powiązania z innymi komponentami encyjnymi Analogia do związków między tabelami w relacyjnej bazie danych
Trwałość zarządzana przez kontener 137 CMP - Container-Managed Persistence Kontener EJB obsługuje wszystkie operacje na bazie danych, których wymaga komponent Kod komponentu nie zawiera żadnych instrukcji SQL Przenaszalność dzięki niezależności od konkretnej bazy danych Kontener generuje odwołania do bazy danych w oparciu o abstrakcyjny schemat podany w deskryptorach instalacji Kontener EJB obsługuje również związki między komponentami encyjnymi CMR Container-Managed Relationships
Trwałość zarządzana przez komponent 138 BMP - Bean-Managed Persistence Twórca komponentu dostarcza kod zarządzający trwałością komponentu: Implementując metody callback do odczytu, zapisu i manipulacji danymi oraz zarządzania związkami np. ejbload(), ejbstore(), ejbcreate(), ejbpostcreate(), ejbremove(), ejbfindxxx() Kod komponentu zawiera wywołania JDBC / SQLJ Ograniczona przenaszalność kod odwołuje się do konkretnego schematu bazy danych Elastyczność twórca komponentu ma "pełną władzę" nad zarządzaniem jego trwałością Niekoniecznie komponent musi odpowiadać jednej tabeli Dane mogą być składowane np. w LDAP, a nie tylko w bazie danych!
CMP czy BMP? 139 CMP BMP
Składniki encyjnego komponentu EJB 140 Interfejs domowy (Home / LocalHome) Wykorzystywany przez klientów do tworzenia, usuwania i wyszukiwania obiektów encyjnych Interfejs komponentowy (Remote / Local) Zawiera deklaracje metod biznesowych komponentu, udostępnianych klientom Klasa komponentu Reprezentuje dane Zawiera metody dostępu do danych i manipulacji danymi Klasa klucza głównego Wykorzystywana do identyfikacji instancji komponentu Deskryptor instalacji (deployment descriptor) Plik XML zawierający informacje dla kontenera
Interfejs domowy 141 Interfejs Home dla klientów zdalnych (i lokalnych) Rozszerza javax.ejb.ejbhome Interfejs LocalHome dla klientów lokalnych Rozszerza javax.ejb.ejblocalhome Zawiera metody do: Tworzenia obiektów encyjnych: 0 lub więcej metod create() Typowo dodające nowy wiersz do tabeli Zwracające interfejs komponentowy Wyszukiwania obiektów encyjnych : metody findxxx() Zwracające interfejs komponentowy lub Collection Obowiązkowo wśród nich findbyprimarykey() Usuwania obiektów encyjnych : remove() Typowo usuwające wiersz z tabeli Podstawowa wersja jako parametr przyjmuje klucz główny Opcjonalnie inne metody nieodnoszące się do konkretnej instancji
Interfejs komponentowy 142 Interfejs Remote dla klientów zdalnych (i lokalnych) Rozszerza javax.ejb.ejbobject Interfejs Local dla klientów lokalnych Rozszerza javax.ejb.ejblocalobject Zawiera metody getxxx/setxxx odczytujące i modyfikujące poszczególne pola Opcjonalnie może zawierać metody biznesowe Dziedziczy metody: getejbhome() lub getejblocalhome() getprimarykey()
Klasa klucza głównego 143 Klucz główny Jednoznacznie identyfikuje instancje komponentu encyjnego Wykorzystywany przy usuwaniu instancji Podstawowe kryterium selekcji przy wyszukiwaniu instancji Klasa klucza głównego Musi implementować java.io.serializable Definiowana przede wszystkim przy kluczach złożonych Dla kluczy prostych (pojedynczy atrybut) można wykorzystać jedną z klas bibliotecznych np. java.lang.string java.lang.integer java.lang.long...
Klasa komponentu 144 Musi implementować interfejs javax.ejb.entitybean Dla każdej metody create() interfejsu domowego zawiera: ejbcreate() inicjalizującą trwałe pola (persistent fields) ejbpostcreate() inicjalizującą pola związków między komp. EJB Implementuje metody: Wyszukujące poprzez ejbfindxxx() Inne metody interfejsu domowego poprzez ejbhomexxx() Metody callback z interfejsu javax.ejb.entitybean: ejbload(), ejbstore(), ejbactivate(), ejbpassivate(), ejbremove(), setentitycontext(...), unsetentitycontext() Metody biznesowe zawarte w interfejsie komponentowym Opcjonalnie inne prywatne metody
Cykl życia encyjnego EJB 145 Brak newinstance() setentitycontext() unsetentitycontext() Pasywny (w puli) ejbcreate() ejbpostcreate() ejbremove() ejbactivate() ejbpassivate() ejbload() Gotowy ejbstore()
146 Tworzenie encyjnego komponentu EJB CMP - Przykład (1/8) Tworzenie komponentów encyjnych CMP w oparciu o tabele
147 Tworzenie encyjnego komponentu EJB CMP - Przykład (2/8) Kreator: wybór tabel z bazy danych Planowane wykorzystanie z serwletu, więc interfejs Local
148 Tworzenie encyjnego komponentu EJB CMP - Przykład (3/8) Utworzone pliki dla komponentu encyjnego Emp: EmpLocalHome.java interfejs LocalHome EmpLocal.java interfejs Local EmpBean.java klasa komponentu ejb-jar.xml standardowy deskryptor instalacji orion-ejb-jar.xml specyficzny dla OC4J deskryptor instalacji JDev 9i JDev 10g
149 Tworzenie encyjnego komponentu EJB CMP - Przykład (4/8) Interfejs LocalHome (EmpLocalHome.java) package mypackage1; import javax.ejb.ejblocalhome; import javax.ejb.createexception; import javax.ejb.finderexception; import java.util.collection; public interface EmpLocalHome extends EJBLocalHome { EmpLocal create() throws CreateException; EmpLocal create(long empno) throws CreateException; EmpLocal findbyprimarykey(long primarykey) throws FinderException; } Collection findall() throws FinderException;
150 Tworzenie encyjnego komponentu EJB CMP - Przykład (5/8) Interfejs Local (EmpLocal.java) package mypackage1; import javax.ejb.ejblocalobject; public interface EmpLocal extends EJBLocalObject { Long getempno(); void setempno(long newempno); String getename(); void setename(string newename);... Long getsal(); void setsal(long newsal); Metody getxxx i setxxx dla pól komponentu } Long getdeptno(); void setdeptno(long newdeptno);
151 Tworzenie encyjnego komponentu EJB CMP - Przykład (6/8) Klasa komponentu (EmpBean.java) package mypackage1.impl; import javax.ejb.entitybean; import javax.ejb.entitycontext; import java.sql.timestamp; public abstract class EmpBean implements EntityBean { private EntityContext context; public Long ejbcreate() { return null; } public void ejbpostcreate() {} public Long ejbcreate(long empno) { setempno(empno); return empno; } public void ejbpostcreate(long empno) {} public void ejbactivate() {} public void ejbload() {} public void ejbpassivate() {} public void ejbremove() {} public void ejbstore() {} public void setentitycontext(entitycontext ctx) { this.context = ctx; } public void unsetentitycontext() { this.context = null; }...... public abstract Long getempno(); public abstract void setempno( Long newempno); public abstract String getename(); public abstract void setename( String newename); public abstract Long getsal(); public abstract void setsal( Long newsal); public abstract Long getdeptno(); public abstract void setdeptno( Long newdeptno); } Metody z interfejsów Local i LocalHome CMP setxxx/getxxx abstrakcyjne CMP wiele spośród "callback" pustych CMP brak metod ejbfindxxx
152 Tworzenie encyjnego komponentu EJB CMP - Przykład (7/8) Standardowy deskryptor instalacji (ejb-jar.xml) <?xml version = '1.0' encoding = 'windows-1250'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <enterprise-beans> <entity> <description>entity Bean ( CMP )</description> <display-name>emp</display-name> <ejb-name>emp</ejb-name> <local-home>mypackage1.emplocalhome</local-home> <local>mypackage1.emplocal</local> <ejb-class>mypackage1.impl.empbean</ejb-class> <persistence-type>container</persistence-type> <prim-key-class>java.lang.long</prim-key-class> <reentrant>false</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>emp</abstract-schema-name> <cmp-field> <field-name>empno</field-name> </cmp-field> <cmp-field> <field-name>ename</field-name> </cmp-field>... <cmp-field> <field-name>sal</field-name> </cmp-field> <cmp-field> <field-name>deptno</field-name> </cmp-field> <primkey-field>empno</primkey-field> </entity> </enterprise-beans> </ejb-jar> Składniki komponentu Abstrakcyjny schemat
153 Tworzenie encyjnego komponentu EJB CMP - Przykład (8/8) Specyficzny dla OC4J deskryptor instalacji (orion-ejb-jar.xml) <?xml version = '1.0' encoding = 'windows-1250'?> <!DOCTYPE orion-ejb-jar PUBLIC "-//Evermind//DTD Enterprise JavaBeans 1.1 runtime//en" "http://xmlns.oracle.com/ias/dtds/orion-ejb-jar.dtd"> <orion-ejb-jar> <enterprise-beans> <entity-deployment name="emp" data-source="jdbc/marek8ids" table="emp"> <primkey-mapping> <cmp-field-mapping name="empno" persistence-name="empno" persistence-type="number(4)"/> </primkey-mapping> <cmp-field-mapping name="empno" persistence-name="empno" persistence-type="number(4)"/> <cmp-field-mapping name="ename" persistence-name="ename" persistence-type="varchar2(10)"/>... <cmp-field-mapping name="sal" persistence-name="sal" persistence-type="number(7,2)"/> <cmp-field-mapping name="deptno" persistence-name="deptno" persistence-type="number(2)"/> </entity-deployment> </enterprise-beans> <assembly-descriptor> <default-method-access> <security-role-mapping impliesall="true" name="<default-ejb-caller-role>"/> </default-method-access> </assembly-descriptor> </orion-ejb-jar> Mapowanie abstrakcyjnego schematu na schemat konkretnej bazy danych
Korzystanie z encyjnych EJB (1/3) 154 Definicja referencji w deskryptorze instalacji: web.xml dla odwołań z serwletów i JSP ejb-jar.xml dla odwołań z innych EJB web.xml <web-app>... <ejb-local-ref> <ejb-ref-name> ejb/emp </ejb-ref-name> <ejb-ref-type> Entity </ejb-ref-type> <local-home> mypackage1.emplocalhome </local-home> <local> mypackage1.emplocal </local> </ejb-local-ref> </web-app>
Korzystanie z encyjnych EJB (2/3) 155 Wyszukiwanie metodą findbyprimarykey() try { Context context = new InitialContext(); EmpLocalHome lh = (EmpLocalHome) context.lookup("java:comp/env/ejb/emp"); EmpLocal el = lh.findbyprimarykey(new Long("7499")); out.println("7499: " + el.getename()); } catch (Exception e) { out.println(e); } 7499: ALLEN Wyszukiwanie metodą findall() try { Context context = new InitialContext(); EmpLocalHome lh = (EmpLocalHome) context.lookup("java:comp/env/ejb/emp"); out.println("employees: "); Collection emps = lh.findall(); Iterator it = emps.iterator(); while (it.hasnext()) { el = (EmpLocal) it.next(); out.println(el.getempno() + ": " + el.getename() + ", " + el.getsal()); } } catch (Exception e) { out.println(e); } Employees: 7369: SMITH, 800 7499: ALLEN, 1600 7521: WARD, 1250 7566: JONES, 2975 7654: MARTIN, 1250 7698: BLAKE, 2850 7782: CLARK, 2450 7788: SCOTT, 3000 7839: KING, 5000 7844: TURNER, 1500 7876: ADAMS, 1100 7900: JAMES, 950 7902: FORD, 3000 7934: MILLER, 1300
Korzystanie z encyjnych EJB (3/3) 156 Tworzenie, modyfikacja i usuwanie danych try { Context context = new InitialContext(); EmpLocalHome lh = (EmpLocalHome) context.lookup("java:comp/env/ejb/emp"); // dodanie nowego pracownika EmpLocal el = lh.create(new Long("9999")); // modyfikacja danych pracownika el.setename("bush"); el.setsal(new Long("4500")); // usuniecie pracownika lh.remove(new Long("9999")); } catch (Exception e) { out.println(e); } Wykorzystanie metody create utworzonej przez kreator (lepszym rozwiązaniem jest dodanie nowej metody create z "pełnym" zestawem parametrów)
Metody wyszukujące findxxx() 157 Do wyszukiwania instancji encyjnych EJB służą metody findxxx() zawarte w interfejsie Home / LocalHome Zwracają interfejs komponentowy lub typ Collection BMT: twórca komponentu implementuje odpowiednie metody ejbfindxxx() w klasie komponentu CMT: dla metod findxxx() zadeklarowanych w interfejsie Home / LocalHome, deskryptor instalacji zawiera odpowiednie zapytania w języku EJB QL Nie dotyczy findbyprimarykey() i findall(), które są automatycznie implementowane przez kontener
CMP: Dodatkowe metody findxxx() Przykład (1/2) Dodanie metody wyszukującej wg pensji 158 ejb-jar.xml </ejb-jar> </enterprise-beans> </entity>... <query> <query-method> <method-name> findbyminsal </method-name> <method-params> <method-param> int </method-param> </method-params> </query-method> <ejb-ql> SELECT DISTINCT OBJECT(e) FROM Emp e WHERE e.sal >?1 </ejb-ql> </query> </entity> </enterprise-beans> </ejb-jar> Zapytanie odwołujące się do abstrakcyjnego schematu zdefiniowanego w deskryptorze instalacji, wyrażone w języku EJB QL
CMP: Dodatkowe metody findxxx() Przykład (2/2) 159 Wyszukiwanie metodą findbyminsal() try { Context context = new InitialContext(); EmpLocalHome lh = (EmpLocalHome) context.lookup("java:comp/env/ejb/emp"); out.println("rich Employees: "); Collection emps = lh.findbyminsal(1280); Iterator it = emps.iterator(); while (it.hasnext()) { el = (EmpLocal) it.next(); out.println(el.getempno() + ": " + el.getename() + ", " + el.getsal()); } } catch (Exception e) { out.println(e); } Rich Employees: 7499: ALLEN, 1600 7566: JONES, 2975 7698: BLAKE, 2850 7782: CLARK, 2450 7788: SCOTT, 3000 7839: KING, 5000 7844: TURNER, 1500 7902: FORD, 3000 7934: MILLER, 1300
Język EJB QL 160 Język zapytań dla EJB oparty o SQL-92 Wykorzystywany do deklaratywnego specyfikowania działania metod wyszukujących w komponentach encyjnych CMP Zapewnia przenaszalność zapytań Zapytania definiowane w kontekście abstrakcyjnego schematu (definiowanego w deskryptorze instalacji) Podczas instalacji zapytania tłumaczone na język danej platformy Składnia SELECT... FROM... WHERE... Brak klauzul ORDER BY, GROUP BY, HAVING (!) SELECT DISTINCT OBJECT(e) FROM Emp e WHERE e.sal >?1 Eliminacja duplikatów Zwracanie obiektów Nazwa z abstrakcyjnego schematu Wyrażenie ścieżkowe Pierwszy parametr
Powiązania między encyjnymi EJB 161 Zazwyczaj w modelu danych występują związki między danymi W bazie danych istnieją związki między tabelami (poprzez klucze obce) W aplikacji J2EE związki te mogą być reprezentowane jako powiązania między encyjnymi komponentami EJB Podstawowe własności związku to: Liczebność liczba instancji biorących udział w związku: 1:1, 1:N, M:N Kierunkowość kierunki w których możliwa jest nawigacja wg związku: związki jedno- i dwukierunkowe Dla komponentów CMP kontener zarządza związkami CMRs: Container-Managed Relationships (od EJB 2.0) Związki wykorzystują lokalne interfejsy docelowych EJB
Tworzenie związków 162 Uwagi o implementacji związków: Dostępne tylko dla EJB 2.0 Dla komponentów opisanych w tym samym deskryptorze instalacji Definicja związku obejmuje: W klasie komponentu: abstrakcyjne metody getxxx/setxxx dla każdego pola reprezentującego powiązanie W deskryptorze instalacji: Nazwa związku, liczebność i kierunkowość Dla każdego kierunku informacje o polu reprezentującym związek Opcjonalnie dla jednego kierunku specyfikacja kaskadowego usuwania Tworzenie związków za pomocą JDevelopera "Ręczna" edycja modułu EJB Wykorzystanie kreatora tworzącego encyjne EJB na podstawie tabel
163 Tworzenie związku CMR - Przykład (1/2) Nowy komponent encyjny Dept (CMP), powiązany z wcześniej utworzonym Emp (CMP) Utworzenie związków z wcześniej utworzonymi encyjnymi EJB w oparciu o klucze obce z bazy danych
164 Tworzenie związku CMR - Przykład (2/2) Właściwości utworzonego związku
CMR: Metody do obsługi związku 165 Metody utworzone przez kreator do obsługi związku: Interfejs EmpLocal komponentu Emp: DeptLocal getdept_deptno() (zamiast Long getdeptno()) void setdept_deptno(deptlocal newdept_deptno) (zamiast void setdeptno(long newdeptno)) Interfejs DeptLocal komponentu Dept: Collection getemp_deptno() void setemp_deptno(collection newemp_deptno)
CMR: Nawigacja w oparciu o związek - Przykład 166 try { Context context = new InitialContext(); EmpLocalHome lh = (EmpLocalHome) context.lookup("java:comp/env/ejb/emp"); EmpLocal el = lh.findbyprimarykey(new Long("7499")); out.println("employee #7499's department:"); out.println("7499: " + el.getename() + ", " + el.getdept_deptno().getdname()); out.println("employees in the same department as Emp #7499:"); Odczyt nazwy departamentu dla obiektu Dept DeptLocal dl = el.getdept_deptno(); Collection emps = dl.getemp_deptno(); Iterator it = emps.iterator(); while (it.hasnext()) { EmpLocal e = (EmpLocal) it.next(); if (!e.isidentical(el)) out.println(e.getempno() + ": " + e.getename()); } } catch (Exception e) { out.println(e); } Odczyt obiektu Dept związanego z danym obiektem Emp Odczyt kolekcji obiektów Emp związanych z danym obiektem Dept Test czy 2 obiekty EJB są identyczne Employee #7499's department: 7499: ALLEN, SALES Employees in the same department as Emp #7499: 7521: WARD 7654: MARTIN 7698: BLAKE 7844: TURNER 7900: JAMES
Transakcje zarządzane przez kontener 167 W kodzie aplikacji nie ma instrukcji rozpoczynających i kończących transakcje Funkcjonalność ta jest realizowana przez kontener w oparciu o informacje z pliku deployment descriptor CMT to jedyna opcja dla encyjnych EJB (dla sesyjnych wybór między CMT lub BMT)... <assembly-descriptor> <container-transaction> <description>no description</description> <method> <ejb-name>myemployee</ejb-name> <method-name>*</method-name> </method> <trans-attribute>requiresnew</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar> ejb-jar.xml
Transakcje zarządzane przez kontener 168 atrybut transakcyjny Znaczenie wartości atrybutu transakcyjnego: Komponent encyjny korzystający z CMP, CMR musi działać w ramach transakcji (!) Dozwolone wartości atrybutu transakcyjnego: Required, RequiresNew, Mandatory
Transakcje zarządzane przez kontener 169 wycofanie transakcji Kod komponentu korzystającego CMT nie może zawierać instrukcji kończących transakcję (commit(), rollback()) Komponent może zlecić kontenerowi wycofanie transakcji wołając metodę setrollbackonly() na rzecz swego kontekstu EJBContext Referencja na obiekt EJBContext jest zapisywana w jednym z pól komponentu przez metodę setentitycontext() na początku cyklu życia Typowo metoda wywołuje setrollbackonly() przed rzuceniem wyjątku Samo rzucenie wyjątku nie wycofuje transakcji (!) Metoda może sprawdzić czy transakcja została "zaznaczona do wycofania" metodą getrollbackonly()... context.setrollbackonly();...
Dostęp do encyjnych EJB z poziomu JSP przy użyciu biblioteki znaczników OJSP EJB 170 Biblioteka znaczników JSP dla EJB może być wykorzystana zarówno do operacji na komponentach sesyjnych jak i encyjnych Plik opisu biblioteki: ejbtaglib.tld musi być instalowany z aplikacją W Oracle ias biblioteka należy do "dobrze znanych" Znaczniki usehome, usebean, createbean zachowują swe znaczenie Typowe dla komponentów encyjnych jest użycie znacznika iterate: Do utworzenia kolekcji instancji encyjnych EJB i iteracji po niej
Dostęp do encyjnych EJB z poziomu JSP przy użyciu biblioteki znaczników OJSP EJB- Przykład Salaries.jsp <%@ page contenttype="text/html;charset=windows-1250"%> <%@ taglib uri="/web-inf/ejbtaglib.tld" prefix="ejb" %> <html> <head> <title>employees</title> </head> <body> <ejb:usehome id="home" type="mypackage1.emplocalhome" location="java:comp/env/ejb/emp" local="true"/> <ejb:iterate id="emp" type="mypackage1.emplocal" collection="<%=home.findall()%>" max="50"> Employee #<%=emp.getempno()%>: <%=emp.getename()+" "+ emp.getsal()%>$. <br> </ejb:iterate> </body> </html> Atrybut local konieczny przy korzystaniu z interfejsów lokalnych <%@ page contenttype="text/html;charset=windows-1250"%> <%@ taglib uri="/web-inf/ejbtaglib.tld" prefix="ejb" %> <html> <head> <title>employees</title> </head> <body> <ejb:usehome id="home" type="mypackage1.emplocalhome" location="java:comp/env/ejb/emp" local="true"/> <ejb:iterate id="emp" type="mypackage1.emplocal" collection="<%=home.findall()%>" max="50"> Employee #<jsp:getproperty name="emp" property="empno" />: <jsp:getproperty name="emp" property="ename" /> <jsp:getproperty name="emp" property="sal" />$. <br> </ejb:iterate> </body> </html> Możliwość korzystania jak z JavaBeans 171