"
 
 1 Einführung in JavaServer Faces
"
 
 2 Die Konzepte von JavaServer Faces
"
 
 3 Standard-JSF-Komponenten
"
 
 4 Advanced JSF
"
 
 5 Verwaltung von Ressourcen
"
 
 6 Die eigene JSF-Komponente
"
 
 7 Ajax und JSF
"
 
 8 JSF und HTML5
"
 
 9 JSF und CDI
"
 
 10 PrimeFaces -- JSF und mehr
"
 
 11 Faces-Flows
"
 
 12 MyGourmet Fullstack -- JSF, CDI und JPA mit CODI kombiniert
"
 
 13 JSF und Spring
"
 
 14 MyGourmet Fullstack Spring -- JSF, Spring, Orchestra und JPA kombiniert
"
 
 15 Tobago -- JSF und mehr
"
 
 16 Eine kurze Einführung in Maven
"
 
 17 Eclipse
"
 
 18 Autoren
"
 
 19 Änderungshistorie

12 MyGourmet Fullstack -- JSF, CDI und JPA mit CODI kombiniert

In diesem Kapitel werden wir anhand unseres MyGourmet -Beispiels die Architektur einer JSF-Anwendung mit CDI und Apache MyFaces CODI vorstellen, die sich in dieser oder ähnlicher Form in der Praxis bewährt hat. Sie stellt eine optimale Ausgangsbasis für Ihre eigenen JSF-Webapplikationen dar - auch für komplexere Anwendungen. Das Beispiel MyGourmet Fullstack ist eine Erweiterung des Beispiels MyGourmet 17 , in dem zum einen die Funktionalität der Anwendung ausgebaut wird und zum anderen die Architektur der Anwendung auf den derzeitigen Stand der Technologie gebracht wird.
Als JSF-Implementierung kommt standardmäßig Apache MyFaces zum Einsatz. Das Beispiel funktioniert aber auch mit der aktuellen Version von Mojarra . Wie in allen anderen Beispielen können Sie auch hier die JSF-Implementierung über ein Profil in der Datei pom.xml bestimmen (siehe Anhang Kapitel:  Eine kurze Einführung in Maven für Details). Zur Persistierung von Daten wird die Java Persistence API (JPA) in Version 2.0 mit dem Hibernate EntityManager als Implementierung eingesetzt. Datenbankseitig steht HyperSQL Data-Base (HSQLDB) bereit - es kann aber jede Datenbank, die von Hibernate unterstützt wird, verwendet werden.
Den Quellcode zu MyGourmet Fullstack finden Sie wie den Code aller bisherigen Beispiele unter http://jsfatwork.irian.at .

12.1 Architektur von MyGourmet Fullstack

In MyGourmet Fullstack kommt eine bei der Java-Webentwicklung weitverbreitete Architektur mit drei übereinanderliegenden Schichten zum Einsatz. Die Präsentationsschicht ist die oberste Schicht und beinhaltet die Benutzerschnittstelle. Sie greift auf die Serviceschicht zu, in der die Geschäftslogik der Anwendung definiert ist. Ganz unten liegt die Datenzugriffsschicht , in der sämtliche in der Serviceschicht getätigten Zugriffe auf die Daten der Anwendung gekapselt sind. Die Entitäten des hinter der Anwendung liegenden Modells sind schichtübergreifend verfügbar. Abbildung Architektur von MyGourmet Fullstack zeigt eine grafische Darstellung der Architektur von MyGourmet Fullstack .
Abbildung:Architektur von MyGourmet Fullstack
Ein Vorteil der Schichtenarchitektur ist die strikte Trennung der Zuständigkeiten (Separation of Concerns, SOC). Jede Schicht ist dabei für einen bestimmten Bereich der Anwendung zuständig. Eine Schicht kann auf die Funktionalität der direkt unter ihr liegenden Schicht zugreifen und kann Funktionalität für die direkt über ihr liegende Schicht anbieten. Nachdem es nie Abhängigkeiten nach oben geben darf, bildet jede Schicht mit den unter ihr liegenden Schichten ein abgeschlossenes Subsystem.
Wir empfehlen Ihnen, immer eine saubere Schichtentrennung einzuhalten - auch wenn das im ersten Moment wie ein unnötiger Mehraufwand aussieht. Die Trennung (natürlich nur an vernünftigen Stellen) zahlt sich spätestens in der Wartung aus.
In den nächsten Abschnitten werden wir einen kurzen Blick auf alle Schichten von MyGourmet Fullstack inklusive der schichtübergreifenden Entitäten werfen. Die Struktur des Maven-Projekts ist etwas einfacher gehalten und entspricht nicht direkt den Schichten der Anwendung. Aus Gründen der Einfachheit haben wir die Entitäten, die Datenzugriffsschicht und die Serviceschicht im Modul mygourmet-service zusammengefasst. Die Präsentationsschicht befindet sich im Modul mygourmet-webapp . Für umfangreichere Projekte macht es aber durchaus Sinn, jede Schicht in einem eigenen Maven-Modul unterzubringen.

12.1.1 Entitäten

Entitäten bilden das Modell der Geschäftsobjekte und sind ein zentraler Bestandteil jeder Applikation. Entitäten sind einfache JavaBeans mit Eigenschaften und deren Getter- und Setter- Methoden. Sie stellen die objektorientierte Repräsentation der Tabellen in der Datenbank dar. Da sie in allen Schichten verwendet werden, sind sie aus Sicht der Architektur außerhalb der Schichten angesiedelt. Die Entitätsklassen finden Sie im Package at.irian.jsfatwork.domain im Modul mygourmet-service .
Die Information über die Zuordnung zwischen den Tabellen und Spalten der Datenbank und den Entitäten und Eigenschaften ist direkt in den Klassen enthalten. JPA bietet dazu eine Reihe von Annotationen an. Dadurch ist es nicht mehr nötig, Unmengen von XML-Dateien zur Konfiguration dieser Zuordnung anzulegen.
Die benutzerdefinierten Bean-Validation-Validatoren müssen sich ebenfalls im Modul mygourmet-service befinden, da sie in den Entitäten benutzt werden. Nachdem Bean-Validation aber nicht von JSF abhängig ist, stellt das kein Problem dar. Die Validatorklassen und Annotationen finden Sie in at.irian.jsfatwork.validation .

12.1.2 Datenzugriffsschicht

In der Datenzugriffsschicht wird die Schnittstelle zur Datenbank mit einem generischen CRUD-Service realisiert. Dieser Service ist in der Klasse CrudService als CDI-Bean im Application-Scope umgesetzt und nutzt die JPA-Unterstützung von CODI. Der CRUD-Service stellt generische Methoden für die wichtigsten Funktionen des Entity-Managers von JPA bereit. Für MyGourmet reicht diese simple Umsetzung aus - in komplexeren Applikationen kann ein solcher CRUD-Service dann zum Beispiel als Grundlage für Repositories dienen. Listing MyGourmet Fullstack: CrudService zeigt Teile der Klasse CrudService .
@ApplicationScoped
public class CrudService {
  @Inject
  private EntityManager em;

  public <T extends BaseEntity> void persist(T entity) {
    em.persist(entity);
  }

  public <T extends BaseEntity> T findById(
      Class<T> clazz, long id) {
    return em.find(clazz, id);
  }

  public <T extends BaseEntity> void delete(T entity) {
    em.remove(entity);
  }
}
Die zentrale Stelle zur Interaktion mit dem Persistenzkontext von JPA ist die Klasse EntityManager . In MyGourmet wird der Entity-Manager wie üblich in der persistence.xml konfiguriert und von CDI verwaltet. Der CRUD-Service bekommt den Entity-Manager dann direkt als CDI-Bean in ein mit @Inject annotiertes Feld eingeimpft.
Der Entity-Manager wird allerdings nicht automatisch als CDI-Bean zur Verfügung gestellt - darum müssen wir uns selbst kümmern. Mit einer Producer-Methode ist das aber mit wenigen Zeilen Code erledigt. Listing MyGourmet Fullstack: Producer für Entity-Manager zeigt die Klasse EntityManagerProducer mit der Producer-Methode createEntityManager() , die eine Bean vom Typ EntityManager im Request-Scope zur Verfügung stellt. Damit ist gewährleistet, dass alle Beans, die eine Abhängigkeit auf den Entity-Manager definiert haben, pro Request immer dieselbe Instanz eingeimpft bekommen.
Die von der Producer-Methode zur Verfügung gestellte Entity-Manager-Instanz kommt aus der Bean EntityManagerProducer . CDI wertet dazu beim Erzeugen der Bean die Annotation @PersistenceContext aus und setzt den Entity-Manager in die annotierte Eigenschaft. Die Bean ist im Dependent-Scope definiert, damit die Producer-Methode wirklich für jeden Request einen neuen Entity-Manager zur Verfügung stellt.
@Dependent
public class EntityManagerProducer {

  @PersistenceContext(unitName = "mygourmet")
  private EntityManager entityManager;

  @Produces
  @RequestScoped
  public EntityManager createEntityManager() {
    return this.entityManager;
  }

  public void dispose(@Disposes EntityManager em) {
    if (em.isOpen()) {
      em.close();
    }
  }
}
Am Ende des Requests wird der Entity-Manager zuerst in der Disposer-Methode dispose() ordnungsgemäß geschlossen und dann aus dem Speicher entfernt. CDI unterstützt Disposer-Methoden für alle von Producer-Methoden erzeugten Beans. Die Methode muss genau einen mit @Disposes annotierten Parameter vom Typ der zu beseitigenden Bean haben.
Die Konfiguration des JPA-Persistenzkontexts erfolgt in der Datei /META-INF/persistence.xml im Maven-Modul mygourmet-service . Dort ist neben der JDBC-Verbindung zu HSQLDB und einigen Einstellungen für Hibernate auch ein Connection-Pool, in unserem Fall C3P0 , eingetragen. Hibernate ist so konfiguriert, dass bei jedem Start der Applikation die Datenbank aus den Mappings in den Entitäten neu erstellt wird. Details finden Sie im Sourcecode des Beispiels.

12.1.3 Serviceschicht

Die Geschäftslogik der Anwendung ist in der Serviceschicht untergebracht und von der Benutzerschnittstelle entkoppelt. Die Serviceschicht bekommt den CRUD-Service injiziert und ruft ihn bei Bedarf, wie zum Beispiel bei einer Persistierung, auf. Der Weg zur Datenbank sollte in der Präsentationsschicht ausschließlich über die Serviceschicht und nicht direkt über die Datenzugriffsschicht erfolgen. Die Serviceklassen befinden sich im Package at.irian.jsfatwork.service und sind als CDI-Beans im Application-Scope definiert. Listing MyGourmet Fullstack: Serviceimplementierung zeigt exemplarisch einen Ausschnitt der Klasse ProviderService .
@ApplicationScoped
public class ProviderService {

  @Inject
  private CrudService crudService;

  @Transactional
  public void save(Provider entity) {
    if (entity.isTransient()) {
      crudService.persist(entity);
    } else {
      crudService.merge(entity);
    }
  }
  @Transactional
  public void delete(Provider provider) {
    provider = crudService.merge(provider);
    for (Order order : provider.getOrders()) {
      order.setCustomer(null);
      crudService.delete(order);
    }
    provider.getCategories().clear();
    crudService.delete(provider);
  }
  @Transactional
  public Provider findById(long id) {
    return crudService.findById(Provider.class, id);
  }
  @Transactional
  public List<Provider> findAll() {
    return crudService.findAll(Provider.class);
  }
}
Die Serviceschicht ist in MyGourmet Fullstack auch für die Transaktionskontrolle der Applikation zuständig. Die Transaktionen sind an einzelnen Servicemethoden ausgerichtet und werden von CODI mit einem Interceptor verwaltet. Jede Methode einer Serviceklasse, die mit der CODI-Annotation @Transactional annotiert ist, wird in einer Transaktion ausgeführt. Wenn die Serviceklasse selbst mit @Transactional annotiert ist, werden alle Methoden der Klasse in einer Transaktion ausgeführt.
Die Serviceschicht ist der ideale Ort zur Definition der Transaktionen, da sie für die Benutzerschnittstelle das Tor zu Geschäftslogik darstellt. Wenn Sie nochmals einen Blick auf Listing MyGourmet Fullstack: CrudService werfen, werden Sie bemerken, dass es dort kein @Transactional gibt. Nachdem CRUD-Operationen nur im Service verwendet werden, laufen sie automatisch in den dort definierten Transaktionen ab.
Die Schichtentrennung ist beispielsweise auch für das Testen außerordentlich wichtig. Die Applikation kann dann nämlich (ohne Ausführung der GUI selbst) über die Serviceschicht direkt getestet werden. Wir empfehlen Ihnen, die Serviceschicht von Beginn an zu testen und die Tests immer auf gleichem Stand wie die GUI-Logik zu halten. Gerade bei der Entwicklung von Webanwendungen kostet jeder zu einem Neustart des Servers führende Fehler, der mit der Ausführung eines Tests verhindert hätte werden können, wertvolle Entwicklungszeit.

12.1.4 Präsentationsschicht

Die Präsentationsschicht umfasst die Benutzerschnittstelle der Applikation und bildet die oberste Schicht der Architektur. In MyGourmet Fullstack besteht die Präsentationsschicht aus der JSF-Webanwendung und ist im Maven -Modul mygourmet-webapp untergebracht. Dieses Modul enthält alle Seitendeklarationen inkusive ihrer Page-Beans sowie alle Komponenten, Konverter, Validatoren und Phase-Listener.
Die Präsentationsschicht ist nur für die Benutzerschnittstelle zuständig und greift zur Ausführung von Geschäftslogik auf die Serviceschicht zu. Umgekehrt darf es aber keine Abhängigkeiten der Serviceschicht oder gar der Datenzugriffsschicht auf die Präsentationsschicht geben. GUI/JSF-Spezifika haben dort natürlich nichts verloren.
Sehen wir uns diesen Vorgang anhand der Anbieter-Übersichtsseite und ihrer Page-Bean etwas genauer an. Listing MyGourmet Fullstack: Page-Bean der Anbieter-Übersichtsseite zeigt die Klasse ProviderListBean der Page-Bean. Beim initialen Zugriff auf die Ansicht wird die Bean inklusive der Konversation erstellt. Beim Laden der Anbieterliste in der Methode preRenderView kommt beim Aufruf von providerService.findAll() im dahinterliegenden CRUD-Service der von der Producer-Methode erzeugte Entity-Manager für den aktuellen Request zum Einsatz. Da die Servicemethode mit @Transactional annotiert ist, startet CODI vor dem eigentlichen Aufruf der Methode eine Transaktion. Beim Verlassen der Methode kümmert sich CODI weiterhin um das korrekte Beenden der Transaktion mit einem Commit im Normalfall und einem Rollback, falls die Methode eine Exception wirft.
@Named
@ViewAccessScoped
public class ProviderListBean implements Serializable {
  @Inject
  private ProviderService providerService;
  private List<Provider> providerList;

  @PreRenderView
  public void preRenderView() {
    providerList = providerService.findAll();
  }
  public List<Provider> getProviderList() {
    return providerList;
  }
  public void deleteProvider(Provider provider) {
    providerService.delete(provider);
  }
}
Dasselbe gilt für das Löschen eines Anbieters in deleteProvider . Beim Aufruf von providerService.delete(provider) wird im dahinterliegenden CRUD-Service ebenfalls die Entity-Manager-Bean aus dem aktuellen Request verwendet. Der zu löschende Anbieter wird beim Aufruf der Action-Methode direkt über die Method-Expression als eine der zuvor geladenen Entitäten übergeben. Im ProviderService muss die Entität daher zuerst mit einem Aufruf der Methode merge an den aktuellen Entity-Manager gebunden werden. Nachdem die Methode delete im Service mit @Transactional annotiert ist, läuft die Operation in einer Transaktion ab.
Damit ist MyGourmet Fullstack fertig konfiguriert und einsatzbereit. Wir möchten Sie dazu einladen, den Quellcode der Anwendung genau unter die Lupe zu nehmen. Betrachten Sie das Beispiel als Basis für eigene Experimente und erkunden Sie die Details der Zusammenarbeit von JSF, JPA, CDI und CODI in der Praxis.