Kwadrans w Bibliotece z JSF 1.2, EJB 3.0 i JPA

Z Jacek Laskowski - Wiki Projektanta Java EE

Na grupie pl.comp.lang.java padło pytanie hibernate one-to-many - czy tak? o Java Persistence (JPA) i sekwencję metod, jakie należy wywołać, aby uaktualnić autora książki w relacji jeden-do-wielu. Nie byłem pewien odpowiedzi, więc postanowiłem zestawić aplikację zanim udzielę odpowiedzi. Będąc członkiem zespołu NetCAT 6.0 (NetBeans Community Acceptance Test) postanowiłem upiec dwie pieczenie na jednym ogniu i nie tylko stworzyć aplikację, ale i sprawdzić możliwości NetBeans IDE 6.0 w sprawnym skonstruowaniu aplikacji Java EE 5. Okazało się, że to, co sądziłem, że zabierze mi 5 minut, zabrało mi faktycznie znacznie więcej ze względu na wiele tematów pobocznych. Udało mi się jednak zgłębić podstawowy temat oraz w nagrodę kilka innych problemów związanych ze zrozumieniem specyfikacji Korporacyjnej Javy 5 (Java EE 5), które nękały mnie od dawna, ale nie były na tyle uciążliwe, abym się nimi zajął.

Celem artykułu będzie stworzenie aplikacji Biblioteka typu CRUD (tworzenie-odczyt-modyfikacja-usunięcie - bez możliwości kasowania), która składa się z interfejsu użytkownika zbudowanego w JavaServer Faces 1.2 (JSF) wraz z częścią biznesową opartą o ziarna EJB 3.0 z wykorzystaniem encji JPA 1.0. Wszystko uruchomione jest na serwerze aplikacji Java EE 5 - Glassfish v2. Przy tworzeniu korzystałem z wersji rozwojowej NetBeans IDE 6.0 Nightly, jednakże było to podyktowane wyłącznie chęcią poddania go testowi prostoty prototypowania niż konkretnemu wymaganiu aplikacji.

Na uwagę zasługuje prostota aplikacji (pod względem konstrukcji jak i funkcjonalności) i niewielka ilość kodu źródłowego. Wszystkie funkcjonalności dostępne w serwerze zostały wykorzystane - wstrzeliwanie zależności, zarządzanie trwałością danych i transakcje znacząco minimalizując ilość kodu, który musiał zostać stworzony. Dodając do tego wsparcie NetBeans IDE 6.0 dla tworzenia aplikacji korporacyjnych ilość kodu wprowadzonego manualnie była dodatkowo zmniejszona.

Spis treści

Oprogramowanie

Środowisko składa się z następujących narzędzi:

Zakłada się, że powyższe oprogramowanie jest zainstalowane i działa poprawnie. Instalacja oprogramowania sprowadza się do pobrania i rozpakowania paczek w wybranym katalogu.

Kompletny projekt do zaimportowania do NetBeans IDE 6.0 dostępny jest jako javaee-biblioteka.zip.

Projekt Biblioteka - założenia

  • Założenie 1: Książka ma wyłącznie jednego autora (nierealistyczne, ale na potrzeby artykułu jak najbardziej)

Wniosek: Wyróżniamy encję Autor i Ksiazka w relacji jeden-do-wielu (OneToMany).

Wyróżniamy dwie encje Ksiazka oraz Autor (pakiet pl.jaceklaskowski.biblioteka.encje)

  • Założenie 2: Książka może zmienić autora
  • Założenie 3: Możliwość pobrania wszystkich książek dla danego autora
  • Założenie 4: Możliwość pobrania autora dla danej książki

Wniosek: Relacja jeden-do-wielu będzie dwukierunkowa.

Podprojekt encji Autor i Ksiazka - Biblioteka-encje

Rozpoczynamy od utworzenia modelu naszej aplikacji, który oparty będzie o 2 encje - Autor i Ksiazka. Dedykujemy im osobny projekt o nazwie Biblioteka-encje.

Encja Autor

Najpierw tworzymy encję Autor. Ciekawostką jest wykorzystanie zapytań nazwanych poprzez adnotację @NamedQueries oraz zapytania JOIN FETCH do gorliwego pobrania informacji o książkach danego autora mimo domyślnego opóźnionego ich ładowania (element fetch = LAZY na metodzie odczytującej getKsiazki())

package pl.jaceklaskowski.biblioteka.encje;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import static javax.persistence.CascadeType.ALL;
import static javax.persistence.FetchType.LAZY;

@Entity
@NamedQueries(
{
    @NamedQuery(
        name = "Autor.wszyscyAutorzyZKsiazkami", 
        query = "SELECT a FROM Autor a JOIN FETCH a.ksiazki"
    ),
    @NamedQuery(
        name = "Autor.wszyscyAutorzy", 
        query = "SELECT a FROM Autor a"
    )
})
public class Autor implements Serializable {

    private static final long serialVersionUID = 1L;
    private long id;
    private String imie;
    private String nazwisko;
    private Set<Ksiazka> ksiazki = new HashSet<Ksiazka>();

    public Autor() {
    }

    public Autor(String imie, String nazwisko) {
        this.imie = imie;
        this.nazwisko = nazwisko;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() {
        return id;
    }

    public String getImie() {
        return imie;
    }

    public void setImie(String imie) {
        this.imie = imie;
    }

    @OneToMany(cascade = ALL, mappedBy = "autor", fetch = LAZY)
    public Set<Ksiazka> getKsiazki() {
        return ksiazki;
    }

    public void setKsiazki(Set<Ksiazka> ksiazki) {
        this.ksiazki = ksiazki;
    }

    public String getNazwisko() {
        return nazwisko;
    }

    public void setNazwisko(String nazwisko) {
        this.nazwisko = nazwisko;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (int) id;
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Autor)) {
            return false;
        }
        Autor other = (Autor) object;
        if (this.id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return this.imie + " " + this.nazwisko;
    }
}

Encja Ksiazka

Kolejnym krokiem jest utworzenie encji Ksiazka:

package pl.jaceklaskowski.biblioteka.encje;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name = "Ksiazka.wszystkieKsiazki", query = "SELECT k FROM Ksiazka k")
public class Ksiazka implements Serializable {

    private static final long serialVersionUID = 1L;
    private long id;
    private String tytul;
    private Autor autor;

    public Ksiazka() {
    }

    public Ksiazka(String tytul) {
        this.tytul = tytul;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() {
        return id;
    }

    @ManyToOne
    public Autor getAutor() {
        return autor;
    }

    public void setAutor(Autor autor) {
        this.autor = autor;
    }

    public String getTytul() {
        return tytul;
    }

    public void setTytul(String tytul) {
        this.tytul = tytul;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (int) id;
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Ksiazka)) {
            return false;
        }
        Ksiazka other = (Ksiazka) object;
        if (this.id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return this.tytul;
    }
}

Konfiguracja jednostki trwałej - persistence.xml

Definiujemy jednostkę trwałą o nazwie biblioteka typu JTA z wykorzystaniem źródła danych zarządzanego przez serwer aplikacyjny - jdbc/__default.

Pliku persistence.xml prezentuje się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
    http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="biblioteka" transaction-type="JTA">
    <jta-data-source>jdbc/__default</jta-data-source>
    <properties>
      <property name="toplink.ddl-generation" value="drop-and-create-tables" />
    </properties>
  </persistence-unit>
</persistence>

Plik zapisujemy w katalogu META-INF.

Podprojekt ziarna EJB PracownikBibliotekiBean - Biblioteka-ejb

Po zdefiniowaniu modelu przystępujemy do utworzenia warstwy dostępowej, którą w naszym przypadku będzie pełniło ziarno EJB PracownikBibliotekiBean.

Interfejs biznesowy - PracownikBibliotekiLocal

Interfejs biznesowy deklaruje 2 metody zmienAutora oraz zarejestrujKsiazke. Jest to interfejs lokalny.

package pl.jaceklaskowski.biblioteka.ejb;

import javax.ejb.Local;
import pl.jaceklaskowski.biblioteka.encje.Autor;
import pl.jaceklaskowski.biblioteka.encje.Ksiazka;

@Local
public interface PracownikBibliotekiLocal {

    void zmienAutora(Ksiazka ksiazka, Autor autor);

    void zarejestrujKsiazke(String tytulKsiazki, String imieAutora, String nazwiskoAutora);
    
}

Klasa implementacji ziarna - PracownikBibliotekiBean

Klasa implementacji ziarna PracownikBibliotekiBean dostarcza implementacji interfejsu biznesowego. Deklarujemy ziarno sesyjne bezstanowe, które korzysta z zarządcy trwałego poprzez mechanizm wstrzeliwania zależności.

package pl.jaceklaskowski.biblioteka.ejb;

import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import pl.jaceklaskowski.biblioteka.encje.Autor;
import pl.jaceklaskowski.biblioteka.encje.Ksiazka;

@Stateless
public class PracownikBibliotekiBean implements PracownikBibliotekiLocal {

    @PersistenceContext(unitName = "biblioteka")
    EntityManager em;
    
    Logger logger = Logger.getLogger(PracownikBibliotekiBean.class.getName());

    public void zmienAutora(Ksiazka ksiazka, Autor autor) {
        logger.info("Krok #0: Związuję autora i książkę z kontekstem trwałym");
        autor = em.merge(autor);
        ksiazka = em.merge(ksiazka);

        logger.info("Krok #1: Przypisuję książkę " + ksiazka + " nowemu autorowi " + autor);
        ksiazka.setAutor(autor);
    }

    public void zarejestrujKsiazke(String tytulKsiazki, String imieAutora, String nazwiskoAutora) {
        Ksiazka ksiazka = new Ksiazka(tytulKsiazki);
        Autor autor = new Autor(imieAutora, nazwiskoAutora);
        ksiazka.setAutor(autor);
        autor.getKsiazki().add(ksiazka);
        em.persist(autor);
    }
}

Podprojekt aplikacji internetowej - Biblioteka-war

Jako warstwę kliencką wykorzystamy JavaServer Faces 1.2 (JSF). Aplikacja rozpoczyna działanie prezentacją strony zawierającej listę książek oraz formatkę do tworzenia nowej książki. Wybór książki z listy powoduje wyświetlenie danych książki z możliwością zmiany autora. Zatwierdzenie zmiany powoduje przejście do strony domowej.

Konfiguracja aplikacji internetowej - /WEB-INF/web.xml

Każda aplikacja internetowa musi posiadać plik konfiguracyjny web.xml w katalogu WEB-INF. Jako, że korzystamy z JSF musi zadeklarować, które z zasobów aplikacji są przetwarzane przez JSF. Definiujemy servlet JSF - Faces Servlet - oraz związane z nim adresy *.faces.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <context-param>
    <param-name>com.sun.faces.verifyObjects</param-name>
    <param-value>false</param-value>
  </context-param>
  <context-param>
    <param-name>com.sun.faces.validateXml</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
</web-app>

Konfiguracja aplikacji JSF - faces-config.xml

Każda aplikacja JSF wymaga pliku konfiguracyjnego faces-config.xml. W nim definiuje się przede wszystkim ziarna zarządzane oraz nawigację stron, ale można znaleźć również wiele innych elementów, które rozszerzają podstawową funkcjonalność dostarczaną przez JSF.

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
  <managed-bean>
    <managed-bean-name>biblioteka</managed-bean-name>
    <managed-bean-class>pl.jaceklaskowski.biblioteka.faces.Biblioteka</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
  <navigation-rule>
    <from-view-id>/lista_ksiazek.jsp</from-view-id>
    <navigation-case>
      <from-outcome>sukces</from-outcome>
      <to-view-id>/ksiazka.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>
  <navigation-rule>
    <from-view-id>/ksiazka.jsp</from-view-id>
    <navigation-case>
      <from-outcome>sukces</from-outcome>
      <to-view-id>/lista_ksiazek.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>
</faces-config>

W naszym przypadku zdefiniowaliśmy pojedyńcze ziarno zarządzane JSF - biblioteka o widoczności session, które reprezentowane jest przez klasę pl.jaceklaskowski.biblioteka.faces.Biblioteka. Poza tym mamy definicję dwóch reguł nawigacyjnych - przejście ze strony lista_ksiazek do strony ksiazka.jsp oraz odwrotnie. Przejście między stronami jest możliwe wyłącznie po zwróceniu identyfikatora sukces z akcji w aplikacji.

Plik musi znajdować się w katalogu WEB-INF.

Ziarno zarządzane JSF - Biblioteka

Przyjrzyjmy się ziarnu zarządzanemu biblioteka. Ziarno reprezentowane jest przez klasę pl.jaceklaskowski.biblioteka.faces.Biblioteka. Ziarna zarządzane JSF mogą korzystać z usługi wstrzeliwania zależności dostarczanej przez serwer aplikacyjny Java EE 5, z czego korzystamy do dostępu do zarządcy trwałego oraz ziarna EJB.

Ziarno biblioteka korzysta z ciekawej funkcjonalności JSF - zarządzania modelem poprzez interfejs javax.faces.model.DataModel (w naszym przypadku korzystamy z javax.faces.model.ListDataModel. Za jego pomocą JSF automatycznie dba o przekazanie informacji, jaki wiersz w tabeli został wybrany przez użytkownika.

package pl.jaceklaskowski.biblioteka.faces;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.EJB;
import javax.faces.convert.Converter;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.SelectItem;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import pl.jaceklaskowski.biblioteka.ejb.PracownikBibliotekiLocal;
import pl.jaceklaskowski.biblioteka.encje.Autor;
import pl.jaceklaskowski.biblioteka.encje.Ksiazka;

public class Biblioteka {

    @EJB
    PracownikBibliotekiLocal pracownikBiblioteki;

    @PersistenceContext(unitName = "biblioteka")
    EntityManager em;

    private String tytulKsiazki;
    private String imieAutora;
    private String nazwiskoAutora;
    private DataModel ksiazkiDataModel;
    private List<SelectItem> autorzyList = new ArrayList<SelectItem>();
    private Ksiazka wybranaKsiazka;
    private Autor wybranyAutor;

    public Converter getAutorConverter() {
        return new AutorConverter(this.em);
    }

    public DataModel getWszystkieKsiazki() {
        if (ksiazkiDataModel == null) {
            ksiazkiDataModel = new ListDataModel();
            Query query = em.createNamedQuery("Ksiazka.wszystkieKsiazki");
            ksiazkiDataModel.setWrappedData(query.getResultList());
        }
        return ksiazkiDataModel;
    }

    public Ksiazka getWybranaKsiazka() {
        return wybranaKsiazka;
    }

    public List<SelectItem> getWszyscyAutorzy() {
        autorzyList.clear();
        Query query = em.createNamedQuery("Autor.wszyscyAutorzy");
        for (Autor autor : (List<Autor>) query.getResultList()) {
            autorzyList.add(new SelectItem(autor, autor.toString()));
        }
        return autorzyList;
    }

    public void setWybranyAutor(Autor wybranyAutor) {
        this.wybranyAutor = wybranyAutor;
    }

    public Autor getWybranyAutor() {
        return this.wybranyAutor;
    }

    public String zarejestrujKsiazke() {
        pracownikBiblioteki.zarejestrujKsiazke(tytulKsiazki, imieAutora, nazwiskoAutora);
        ksiazkiDataModel = null; // wyczysc model, aby wymusić jego odświeżenie
        return null;
    }

    public String zmienAutora() {
        System.out.println("Wybrany autor: " + wybranyAutor);
        System.out.println("Wybrana książka: " + wybranaKsiazka);
        pracownikBiblioteki.zmienAutora(wybranaKsiazka, wybranyAutor);
        ksiazkiDataModel = null; // wyczysc model, aby wymusić jego odświeżenie
        return "sukces";
    }

    public String wyswietlKsiazke() {
        wybranaKsiazka = (Ksiazka) ksiazkiDataModel.getRowData();
        return "sukces";
    }

    public String getImieAutora() {
        return imieAutora;
    }

    public void setImieAutora(String imieAutora) {
        this.imieAutora = imieAutora;
    }

    public String getNazwiskoAutora() {
        return nazwiskoAutora;
    }

    public void setNazwiskoAutora(String nazwiskoAutora) {
        this.nazwiskoAutora = nazwiskoAutora;
    }

    public String getTytulKsiazki() {
        return tytulKsiazki;
    }

    public void setTytulKsiazki(String tytulKsiazki) {
        this.tytulKsiazki = tytulKsiazki;
    }
}

Strona domowa aplikacji internetowej - lista_ksiazek.jsp

Przejdźmy do stworzenia strony domowej aplikacji - lista_ksiazek.jsp. Do pozyskania danych wykorzystane jest ziarno zarządzane JSF biblioteka. Wciśnięcie przycisku Zmień autora spowoduje wykonanie metody wyswietlKsiazke, która ostatecznie przeniesie nas do strony ksiazka.jsp zgodnie z definicją reguły nawigacyjnej w pliku faces-config.xml, gdy wynikiem działania akcji będzie identyfikator sukces.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Biblioteka - Lista książek</title>
  </head>
  <body>
    <f:view>
      <h:form>
        Tytuł: <h:inputText value="#{biblioteka.tytulKsiazki}" />
        Imie autora: <h:inputText value="#{biblioteka.imieAutora}" />
        Nazwisko autora: <h:inputText value="#{biblioteka.nazwiskoAutora}" />
        <h:commandButton value="Zarejestruj książkę" action="#{biblioteka.zarejestrujKsiazke}" />
        <br />
        <h1>Lista książek</h1>
        <h:dataTable value="#{biblioteka.wszystkieKsiazki}" var="ksiazka">
          <h:column>
            <h:outputText value="#{ksiazka.tytul}" />
          </h:column>
          <h:column>
            <h:outputText value="#{ksiazka.autor.imie}" />
          </h:column>
          <h:column>
            <h:outputText value="#{ksiazka.autor.nazwisko}" />
          </h:column>
          <h:column>
            <h:commandButton value="Zmień autora" action="#{biblioteka.wyswietlKsiazke}" />
          </h:column>
        </h:dataTable>
      </h:form>
    </f:view>
  </body>
</html>

Strona szczegółów książki - ksiazka.jsp

Strona ksiazka.jsp prezentuje szczegóły wybranej książki z możliwością zmiany autora.

Ciekawostką strony jest wykorzystanie konwertera AutorConverter (o którym za moment). Przekazanie konwertera musi odbyć się poprzez ziarno zarządzane JSF, gdyż konwertery nie podlegają obsłudze wstrzeliwania zależności i niemożliwe byłoby użycie zarządcy trwałego za pomocą adnotacji @PersistenceContext.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Biblioteka - Szczegóły książki</title>
  </head>
  <body>
    <f:view>
      <h:form>
        <h1>Szczegóły książki</h1>
        <h:outputText value="#{biblioteka.wybranaKsiazka.tytul}" />
        <br>
        <h:outputText value="#{biblioteka.wybranaKsiazka.autor.imie}" />
        <br>
        <h:outputText value="#{biblioteka.wybranaKsiazka.autor.nazwisko}" />
        <br>
        <h:selectOneMenu value="#{biblioteka.wybranyAutor}" converter="#{biblioteka.autorConverter}">
          <f:selectItems value="#{biblioteka.wszyscyAutorzy}" />
        </h:selectOneMenu>
        <h:commandButton value="Zmień autora" action="#{biblioteka.zmienAutora}" />
      </h:form>
      <br>
      <h:messages />
    </f:view>
  </body>
</html>

Uruchomienie

Uruchomienie aplikacji za pomocą NetBeans IDE 6.0 Nightly sprowadza się do zdefiniowania projektów i związania ich ze środowiskiem serwera aplikacji Java EE 5, np. Glassfish v2, a następnie wybrania menu Run.

Grafika:netbeans-biblioteka-run.PNG

Uruchomienie aplikacji to otworzenie strony http://localhost:8080/Biblioteka-war/lista_ksiazek.faces, która prezentuje formatkę rejestracji nowych książek wraz z autorami oraz listę już zarejestrowanych pozycji.

Grafika:netbeans-biblioteka-listaksiazek.png

Po dodaniu kilku książek aplikacja wygląda następująco:

Grafika:netbeans-biblioteka-listaksiazek-kilkapozycji.PNG

Wciśnięcie przycisku Zmień autora otworzy kolejną stronę, na której znajdują się szczegóły wybranej książki.

Grafika:netbeans-biblioteka-ksiazka.png

I wybranie innego autora niż aktualnie przypisanego do książki powoduje jego zmianę.

Grafika:netbeans-biblioteka-autorzmieniony.png

Wykonanie czynności raportowane jest w dzienniku zdarzeń serwera następująco:

...
LDR5010: All ejb(s) of [Biblioteka] loaded successfully!
naming.bind
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/Biblioteka-war'
TopLink, version: Oracle TopLink Essentials - 2.0 (Build b58g-fcs (09/07/2007))
Server: unknown
file:/C:/Documents%20and%20Settings/jlaskowski/My%20Documents/NetBeansProjects/Biblioteka/dist/gfdeploy/Biblioteka-encje.jar-biblioteka login successful
Wybrany autor: Agata Laskowska
Wybrana książka: EJB 3.0 w przykładach
Krok #0: Związuję autora i książkę z kontekstem trwałym
Krok #1: Przypisuję książkę EJB 3.0 w przykładach nowemu autorowi Agata Laskowska
Osobiste