Seminarausarbeitung

Thema: Java 2 Enterprise Edition

Autor: Florian Piaszyk, FH Giessen, 26.03.2002

 

 

 


1.Vorwort

 

Dieses Dokument beschreibt die J2EE-Architektur anhand der J2EE 1.3 Spezifikation von Sun. Es handelt sich hierbei um ein Dokument, das sowohl einen allgemeinen Überblick über die Architektur gibt, als auch ein Beschreibung von Java-basierten Internet-Applikationen darstellt. Es wird sozusagen der Spagat zwischen einem „high-level“ Überblick und konkreten technischen Implementierungsrichtlinien gemacht.

 

 


2. Geschichtliche Entwicklung von J2EE

 

Als Mitte der 90er Jahre die Client/Server-Modelle von der 3-Tier Architekturen allmählich abgelöst wurden, entstand der Applikation Server und mit ihm eine Reihe von Implementierungen, Spezifikationen und Standards. Man erinnere sich zum Beispiel an Produkte wir Apptivity von Progress oder an SilverStream, welche damals als Applikation Server zwar in der Lage wahren Java-Code zu verarbeiten, sich jedoch weit entfernt von übergreifenden Standards befanden.

Es folgte die Servlet-Spezifikation und die Enterprise JavaBeans (EJB), welche bis zu einem relativ fortgeschrittenen Grad festlegten, wie ein Java-basierter Server beschaffen sein sollte.

Ein Jahr nach diesen Neuerungen wurde die Java 2 Enterprise Edition (J2EE) geboren. Zahlreiche Hersteller von Applikation Servern, darunter auch SilverStream, bestanden die Zertifizierungs-Tests. Andere hingegen, wie Apptivity, überlebten diese Ära nicht und wurden eingestellt.

Die Architektur des Middle-Tiers, die durch die J2EE-Architektur definierte wird, erfreut sich mittlerweile einer hohen Akzeptanz und somit wird sie von vielen Herstellern als De-Fakto-Industriestandard gesehen.

 

 


3. Die J2EE Technologie

 

 

3.1 Was ist J2EE und wozu dient es?

 

Die Java 2 Enterprise Edition ist ein Sammlung verschiedenster Spezifikationen auf deren Grundlage Softwareprodukte, insbesondere Internetanwendungen, entwickelt werden können.

Die J2EE-Spezifikation kennt die folgenden vier Typen von Komponenten, die von J2EE-kompatiblen Applikation Server unterstützt werden müssen :

 

Auf die einzelnen Komponenten wird weiter unten nochmals genauer eingegangen.

Abbildung 1 : Allgemeines Java 2 Enterprise Edition Applikationsmodell

 

 

Das J2EE Applikationsmodell kapselt die funktionellen Schichten in spezifischen Komponententypen. Businesslogik wird in EJB-Komponenten gekapselt. Die Interaktion von Clients kann mittels reinen HTML Webseiten oder durch Webseiten, die durch Java-Technologie unterstützt werden, realisiert werden. Komponenten kommunizieren transparent unter Verwendung verschiedener Standards:

 

Wie man an der Abbildung 1 erkennen kann, lässt sich mit der J2EE-Architektur eine sehr saubere Trennung zwischen Präsentationslogik und Businesslogik erreichen.

 

 

3.2 Die Architektur

 

Abbildung 2: Java 2 Enterprise Edition Architektur

 

 

Die zentrale Komponente der Architektur ist der Applikation Server, der für die Anwendung die nötige Infrastruktur bereitstellt, wie Datenbankanbindung, Netzwerkressourcenmanagement usw. Darüber hinaus bieten die Applikation Server für die Architekturen, basierend auf verteilten Komponenten, ein entsprechendes Framework für serverseitige Komponenten. Das heutzutage bevorzugte (serverseitige) Komponentenmodell ist das EJB-Framework (Enterprise JavaBeans™), das unter Berücksichtigung von Skalierbarkeit, Transaktionssicherheit und Multi-User-Security von Sun Microsystems entwickelt wurde. Der Applikation Server ist in diesem Kontext mehr als nur ein klassischer, reiner TP-Monitor, da er um die Fähigkeit, auf verteilte Objekte zugreifen zu können, erweitert wurde.

 

Als Clients fungieren Web Browser (z.B. Netscape Navigator, Microsoft Internet Explorer), die HTML verarbeiten und darstellen können, sowie eine JVM mitbringen, d.h. eine Umgebung für Java auf dem Client bereitstellen.

 

Das Deployment der Komponenten erfolgt auf der Middle-Tier, einem J2EE-Applikation Server, der die Laufzeitumgebung für die eingesetzten (Java)-Komponenten darstellt und das Load Balancing, Ressourcen-Management usw. übernimmt. Der J2EE-Applikation Server besteht praktisch aus einem HTTP-Listener und mehreren sog. „Containern“ für Java-Komponenten. Ein Beispiel für einen solchen Applikation Server ist der IBM Web­Sphere Application Server.

 

Das Enterprise Information Tier wird durch einen Datenbank dargestellt. Als Beispiel sei hier MySQL oder Oracle 9i genant.

 

Durch die hier vorgestellte Architektur und durch die eingesetzten Server lässt sich die von Sun geprägte Devise „Write Once, Run Anywhere“ in die Praxis umsetzen.

 

 

3.3 J2EE Plattform Technologien

 

Die J2EE-Plattform vereinigt Technologien, um die Entwicklung sogenannter „Multitier Enterprise Applications“ zu unterstützen. Die Technologien lassen sich in drei Kategorien einordnen: „Komponenten“ (siehe nächsten Abschnitt), „Dienste“ (APIs für Datenbankzugriff, Transaktionsbehandlung, Namensgebung, Verzeichnisse und Nachrichten) und „Kommunikation“ (zwischen Clients und Servern und zwischen kollaborierenden Objekten, die auf eigenen Servern liegen). Zusammen bilden sie ein serverseitiges Komponentenmodell, wobei es eine klare Trennung zwischen der Umgebung (Container) und dem tatsächlichen Server-Objekt (Komponente) selbst gibt.

 

 

3.3.1 Komponenten

 

Unter dem Begriff „Komponente“ wird eine Softwareeinheit auf Applikations­ebene verstanden. Die von der J2EE-Plattform unterstützten Komponenten in diesem Sinne sind Applets, Applikations-Clients, Enterprise JavaBeans™ (EJB) und Web-Komponenten. Die ersten beiden Typen laufen auf einem Client, EJB und Web-Komponenten laufen auf einer Server Plattform.

 

Der systemseitige Rahmen, in dem die Komponenten laufen, heißt „Container“. Die Container selbst sind Bestandteile eines Applikation Servers, der mehrere solcher Container bündelt und somit in gewisser Weise zu einer großen Applika­tionsmanagementstruktur beiträgt. Die Container sind das Herz einer komponen­tenbasierten Umgebung, ähnlich der Wichtigkeit des ORBs in einer CORBA-Architektur. In den Containern werden die Komponenten registriert, erzeugt bzw. verfügbar gemacht  und auch wieder zerstört. Als Beispiele seien der EJB- oder der Servlet-Container angeführt.

 

 

3.3.1.1 Applets und Applikation Clients

 

Applets und Application Clients sind Client-Komponenten, die in ihrer eigenen Java Virtual Machine (JVM) ausgeführt werden. Ein Applet Container stellt die Unterstützung für das „Applet Programming Model“ zur Verfügung (J2SE) und ein Application Client Container sichert den Zugriff auf die J2EE APIs.

 

 

3.3.1.2 Web Komponenten

 

Eine Web-Komponente ist eine Softwareeinheit, die eine „Response“ auf einen „Request“ liefert. Typischerweise bilden diese Komponenten das Benutzerinter­face für eine webbasierte Anwendung. Die J2EE-Plattform spezifiziert zwei Typen von Web Komponenten: Servlets und JavaServer Pages.

 

 

 

 

3.3.1.3 EJB Komponenten

 

Der Terminus „Enterprise JavaBeans™“ steht für ein standardisiertes, serverseitiges Komponentenmodell und für Komponenten-Transaktionsmonitore (im englischen CTM, Component Transaction Monitor). CTM’s sind eine Synthese aus den tradi­tionellen TPM’s (Transaction Processing Monitors) und ORB-Technologien, d.h. sie sind echte Applikation Server. Der Begriff CTM spiegelt dabei die Kernpunkte der eingesetzten Technologie wider: die Benutzung eines verteilten Komponentenmodells, der Fokus auf das Transaktionsmanagement, und das Ressourcen- und Dienstemanagement, das man von diesen Monitoren kennt. CTM’s sind für Business-Objekte das, was relationale Datenbanken für Business-Daten sind. CTM’s kümmern sich um die Funktionalität auf Systemebene und erlauben es dem Applikationsentwickler, sich auf die Probleme der Businesslogik zu konzentrieren.

 

Es gibt zwei Arten von Enterprise JavaBeans: Session Beans und Entity Beans. Ein Session Bean repräsentiert eine vorübergehende Konversation mit einem Client. Falls Server oder Client abstürzen, sind Session Beans und ihre Daten nicht mehr vorhanden. Im Gegensatz dazu sind Entity Beans persistent und repräsentieren Daten aus einer Datenbank. Wenn Client oder Server abstürzen, stellen die dem Bean zugrundeliegenden Dienste sicher, dass die Daten des Entity Beans ge­sichert wurden.

 

 

 

 

3.3.2 Komponenten, Container und Dienste

 

Die J2EE-Komponententypen und ihre zugehörigen Container sind in Abbildung 3 illustriert. Container stellen für die Applikationskomponenten die J2SE-Plattform-APIs bereit, die die Java IDL und JDBC 2.0 Core Enterprise APIs beinhalten. Eine Übersicht über alle APIs der J2EE-Plattform gibt Tabelle 1.

 

Abbildung 3 : J2EE Komponenten und Container

 

 

API

Beschreibung

 

 

EJB

(Enterprise JavaBeans™)

Das Enterprise JavaBeans™ API definiert ein serverseitiges Komponenten­modell zur Implementierung von Geschäftslogik, das Portabilität über ver­schiedene Application Server bietet und eine Reihe von Diensten bereitstellt, so dass Entwickler sich komplett auf die Geschäftslogik konzen­trieren können. Strenggenommen ist EJB nicht wirklich ein API, sondern eher ein Framework für „component based enterprise computing“. Nichts­destoweniger gibt es dennoch ein API innerhalb des EJB Applikations-Framework (javax.ejb und javax.ejb.deployment packages).

JNDI

(Zugriff auf Namens- und Verzeichnis­dienste)

Das Java Naming and Directory Interface ist das Java Enterprise API für den Umgang mit Netzwerknamens­­- und Netzwerkverzeichnisdiensten. JNDI ist generisch, d.h. a priori nicht an ein bestimmtes Protokoll gebunden. So­g. Service Provider unterstützen die Benutzung bestimmter Proto­kolle wie NIS, LDAP oder NDS bzw. die Benutzung von RMI- und CORBA-Objektregistrierdatenbanken.

RMI

(Remote Method Invocation)

RMI (Remote Method Invocation) ist ein Programmiermodell, das einen gene­rischen, high-level Ansatz für verteilte Objekte bietet. Dieses Modell er­weitert das objektorientierte Programmier-Paradigma um die Kopplung verteilter Client-Server Architekturen. Es erlaubt das clientseitige Aus­führen von Methoden nichtlokaler, serverseitiger Objekte. Eine Implemen­tierung von RMI über IIOP (Internet Inter-Orb Protocol) ist das RMI-IIOP, das benutzt werden muss, falls ein Application Client mit Enterprise Beans kommunzieren will.

Java IDL

(CORBA verteilte Objekte)

Das Java Interface Definition Language API erzeugt Remote Interfaces, um eine CORBA-basierte Kommunikation innerhalb der Java2-Plattform zu ermöglichen. Hierbei handelt es sich um eine RMI-Implementierung für heterogene Systeme, in denen Client und Server nicht beide in Java, son­dern in einer beliebigen Sprache geschrieben sind.

Servlets und JSP

Das Servlets- und JavaServer Pages™ API unterstützt dynamische HTML- Erzeugung und Session-Management für Browser-Clients. Das Java Servlet API stellt generische Mechanismen bereit, um die Funktionalität eines Servers zu erweitern, der ein auf Requests und Responses basierendes Protokoll verwendet.

JMS

(Enterprise Messaging)

Das Java Messaging Service API unterstützt asynchrone Kommunikation in verschiedenen Messaging-Systemen, wie z.B. verlässliches Queuing oder Publish-and-Subscribe Services.

JTA/JTS

(Managing distributed trans­actions)

Das Java Transaction API (JTA) dient dazu, verteilte Transaktionen zu managen. Das Java Transaction Service API (JTS) definiert Dienste zum verteilten Transaktionsmanagement, die auf dem CORBA Objekt- Trans­aktionsservice basieren.

JDBC™

(arbeiten mit Datenbanken)

JDBC (Java Database Connectivity) ist das Java Enterprise API, um den Zu­griff auf relationale Datenbanksysteme zu ermöglichen, wie z.B. Informix, Sybase, SQL Server, DB2 oder Oracle.

Tabelle 1: APIs der Java 2 Enterprise Edition

 

 

Primäres Ziel der Java 2 Plattform (Enterprise Edition) ist es, komponentenbasierte Technologien (Enterprise JavaBeans™, JavaServer Pages™ und Servlets) bereit zu stellen, die die Entwicklung von sog. Enterprise Applications (bzw. Appli­kationen, die aus einzelnen verteilten, aber miteinander kommunizierenden  Komponenten bestehen) vereinfachen. Die J2EE-Plattform verfügt über eine Reihe von Diensten auf Systemebene, die eine solche Applikationsprogram­mierung vereinfachen. Darüber hinaus können die Komponenten an eigene Anfor­derungen angepasst werden, um Ressourcen in der Umgebung zu nutzen, in der sie eingespielt („deployed“) werden. In Verbindung mit den komponentenorientier­ten Technologien verfügt die J2EE-Plattform zudem über APIs, die es den Kompo­nenten ermöglichen, auf eine Viel­zahl von nichtlokalen („remote“) Diensten und Mechanismen zur Kommunikation zwischen Clients und Servern bzw. zwischen kollaborierenden Objekten auf verteilten Serverarchitekturen zuzugreifen.

 

 


4. Beispiel für eine J2EE Plattform

 

4.1 Software

 

Wie schon oben erwähnt, ist der Applikation Server die zentrale Komponente in einer J2EE-Architektur. Am Beispiel von Tomcat, JBoss und MySQL soll hier eine solche Plattform erläutert werden.

 

Abbildung 4: J2EE-Architektur als Open-Source Lösung

 

 

4.1.1 Tomcat 4.0

 

Um Java-Servlets und JSPs auf einem Rechner ausführen zu können, braucht man eine passende Engine, die mit den Servlets umgehen kann. Tomcat ist so eine Engine.
Tomcat ist nicht irgendeine Engine, sondern die Referenzimplementierung von der Java Server Pages (JSP) und Servlet Spezifikation.

Wie in Abbildung 4 zu sehen ist, dient Tomcat in dieser Beispielarchitektur nicht nur als JSP und Servlet-Engine, sondern auch als Webserver. Natürlich besteht die Möglichkeit, noch einen Webserver vor die Engine zu positionieren, hier sei der OpenSource Webserver Apache genannt.

 

 

4.1.2 JBoss 2.4.1

 

JBoss ist ein OpenSource Projekt der JBoss-Group. Es handelt sich dabei um einen vollständig in Java implementierten Enterprise JavaBeans Applikations-Server, der modular aufgebaut ist. An der Entwicklung des JBoss EJB Applikation Server sind 1000 Entwickler weltweit beteiligt.

Abbildung 4 zeigt, dass in unserer Beispielarchitektur der JBoss Server für die Ausführung der Session und Entity-Beans verantwortlich ist und somit die eigentliche Businesslogik kapselt.

 

 

4.1.3 MySQL Server 3.23

Die Open Source-Software MySQL ist ein Relationales Datenbank-Management-System (RDBMS). Aufgrund seiner einfachen Handhabung und Schnelligkeit wird es für den Einsatz im Rahmen kleinerer und mittlerer Projekte sehr gerne gewählt. Vor allem in der PHP-Szene ist MySQL mittlerweile de facto Standard.

MySQL versteht einen Grundstock von SQL-Statements. Sie sind allen SQL-RDBMS gemeinsam. Die wichtigsten vier sind INSERT, SELECT, DELETE und UPDATE. Daneben hat beinahe jedes RDBMS eine ganze Reihe eigener Funktionen an Bord, die innerhalb solcher Statements verwendet werden. Der Einsatz im Rahmen eines SELECT ist dabei der häufigste Fall. MySQL bietet unter anderem arithmetische, logische, mathematische sowie Datums-, String- und Kontrollfluß-Funktionen. Sie dienen dazu, bei SQL-Abfragen die Datensätze schon auf der Datenbank-Ebene zu verändern oder vorzuverarbeiten. Dadurch werden die Daten in die benötigte Form gebracht.

MySQL repräsentiert in dieser Applikation das Enterprise Information System. Da bei der Programmierung der Datenbank zugriffe unter Java ( JDBC ) die Integrität der Datenbank ohnehin in Händen des Programmierers liegt sollte, ist es nicht von Nachteil, dass MySQL nicht alle Integritätsbedingungen behandelt.

 

 


5. Beispiel für ein J2EE-Framework

 

5.1 Ablauf einer Anfrage

 

Alle Requests, die mittels HTTP von einem Client kommen, werden von einer Controller-JSP entgegengenommen, die übermittelten Parameter in einer JavaBean (keiner Enterprise JavaBean!) gespeichert und gegebenenfalls geprüft.

Über diese JavaBean und eine sogenannte EJB-Factory wird die entsprechende Session Bean instanziiert und die weiterführende Businesslogik ausgeführt. Dies kann eine weitere Prüfung der Daten, Benutzung von weiteren EJBs (insbesondere Entity Beans) bzw. Anfragen an Datenbank-Prozeduren beinhalten. Die so erhaltenen Daten werden wieder der JavaBean übergeben.

Nach erfolgreicher Ausführung der Businesslogik wird der Request durch die Controller-JSP an eine View-JSP weitergeleitet, die unter Benutzung der JavaBean für die Darstellung der ermittelten Daten verantwortlich ist und eine entsprechende HTML-Seite generiert.

Dieser Zusammenhang ist in Abbildung 5 dargestellt.

 

Abbildung 5: Bearbeitung eines Repuests

 

 

5.2 3Tier Konzept

 

 

5.2.1 Client-Tier

 

Clients laufen für gewöhnlich innerhalb eines Browsers, um den Inhalt, der von der Web Tier bereitgestellt wird, darzustellen. Die Benutzeroberfläche wird von der Web Tier auf dem Server generiert und mittels HTML an den Client übermittelt.

 

 

5.2.2 Protokolle

 

Web Clients benutzen HTTP (Hypertext Transfer Protocol) oder HTTPS als Transport-Protokoll. Diese Protokolle haben mehrere Vorteile:

 

Sie sind sehr weit verbreitet. Fast jeder Computer verfügt heute über einen Browser, der mittels HTTP kommunizieren kann. Somit ist der Einsatz einer Anwendung vereinfacht.

·        Sie sind robust und einfach. Gute Implementierungen von HTTP Servern und Clients sind weit verbreitet.

·        Sie gehen durch Firewalls. Wegen der ausgedehnten Benutzung von HTTP werden typischerweise Firewalls eingesetzt, um HTTP hindurchzulassen. Das macht HTTP zu dem Protokoll der Wahl für das Internet, wo Server und Clients durch Firewalls getrennt sind.

 

Gleichzeitig hat die Einfachheit dieser Protokolle einige Nachteile zur Folge, die größtenteils überwunden werden können:

·        Sie sind sessionless (zustandslos). HTTP ist ein Request-Response Protokoll, das kein Konzept für die Behandlung von Sessions mitliefert. Deswegen werden einzelne Requests unabhängig behandelt. Aber es gibt Wege, um mit der JSP-Technologie eine Sessionbehandlung zu ermöglichen. In der Praxis ist dies also ohne Belang.

·        Sie unterstützen keine Transaktionen. HTTP ist ein Netzwerkprotokoll. Daher bietet HTTP kein Konzept bzgl. Transaktionen oder Sicherheit. Dennoch ist das in der Praxis kein Problem, da Transaktionen und Sicherheit mit EJBs auf dem Server abgehandelt werden.

 

 

5.2.3 Web Browser als Client

 

Der Web Browser ist der einfachste J2EE Client. Er dient dazu, um den von der Web Tier gelieferten HTML Inhalt darzustellen. Mehr und mehr Browser unterstützen JavaScript und DHTML (Dynamic HTML), so dass immer leistungsfähigere Benutzeroberflächen erstellt werden können, die nur einen Web Browser benutzen.

Ein stand-alone Web Browser ist der Web Client der Wahl für das Internet. Web Browser sind weit verbreitet und Benutzer sind vertraut mit ihnen.

 

 

5.2.4 Web Tier

 

Die Möglichkeit, dynamischen Inhalt, der einem spezifischen Benutzer zugewiesen wird, zu generieren, ist ein wichtiger Bestandteil von webbasierten Anwendungen. JavaServer Pagesä (JSP) und Servlets sind J2EE-Technologien, die die Generierung von dynamischen Inhalten plattformunabhängig und portabel unterstützen. Webanwendungen, die diese Technologien Benutzen, können in einer Vielzahl von Varianten aufgebaut werden. Webzentrierte Anwendungen benutzen JSP-Seiten und Servlets oder JSP-Seiten mit modularen Komponenten (JavaBeans). EJB-zentrierte Anwendungen benutzen JSP-Seiten und modulare Komponenten in Verbindung mit Enterprise Java Beans.

 

 

5.2.4.1 Servlets

 

Java Servlets erweitern die Funktionalität eines Webservers. Sie sind portabel, plattform- und Webserver-unabhängig.

Einer ihrer größten Vorteile ist, dass Servlets einheitliche APIs (Application Programming Interface) bereitstellen, um Session Daten in der ganzen Webanwendung aufrecht zu halten und um mit den Benutzeranfragen zu interagieren. Session Daten können genutzt werden, um die Limitierungen von Webanwendungen aufgrund der zustandslosen Natur von HTTP zu umgehen.

 

 

5.2.4.2 JavaServer Pages

 

JavaServer Pagesä (JSP) Technologie bietet einen einfachen Weg, um servlet-basierten, dynamischen Inhalt zu entwickeln, mit dem zusätzlichen Nutzen, dass Inhalt und Anzeigelogik weitestgehend getrennt werden können. Durch eine komponentenbasierte Architektur (Verwendung von JavaBeans) kann Code wiederverwendet werden.

 

Sowohl Servlets als auch JSPs beschreiben, wie ein Request (von einem HTTP Client) verarbeitet wird, um einen Response zu erzeugen.

 

 

5.2.5 Enteprise JavaBeans Tier

 

In einer mehrschichtigen J2EE Anwendung beinhaltet die Enterprise JavaBeansä (EJBä) Schicht anwendungsspezifische Businesslogik und Services wie z.B. Transaktionsmanagement, und Security. EJB-Komponenten sind die fundamentale Verbindung zwischen Präsentationskomponenten, die von der Web Tier bereitgestellt werden, und businesskritischen Daten sowie Systemen, die in der Enterprise Information System Tier gehalten werden.

 

Da verschiedene Hersteller (IBM, Bea, ORACLE, Tomcat, JBoss) die J2EE-Plattform unterschiedlich realisiert haben, ist die Plattformunabhängigkeit beim Deployen von EJBs nicht mehr gegeben. Sollte es zu einem Wechsel der Middle Tier kommen, ist hier mit Mehraufwänden für die Anpassung an den neuen Application Server zu rechnen. Daher sollte unbedingt darauf geachtet werden, EJBs möglichst nach dem Standard zu entwickeln und nicht auf spezifische Funktionalitäten des Application Servers zuzugreifen.

 

 

5.2.5.1 Verwendung von Session Beans

 

Use Cases werden als Session Beans modelliert. Wo sinnvoll können mehrere Use Cases zu einem Session Bean zusammengefasst werden. Soweit möglich werden Session Beans stateless modelliert (da diese vom Container performanter ausgeführt werden können) und nur wo nötig stateful.

 

 

5.2.5.2 Verwendung von Entity Beans

 

Persistente Objekte werden als Entity Beans modelliert. Entity Beans sollten nur von Session Beans aus angesprochen werden.

 

Da die Datenmodelle häufig sehr komplex sind , ist meist nur Bean Managed Persistence (BMP) möglich. Container Managed Persistence (CMP) ist nur in Ausnahmefällen denkbar, wird aber generell angestrebt, da es weniger Codierungsaufwand bedeutet.

 

Mit der Freigabe der Version 1.3 der J2EE-Spezifikation von Sun haben auch die Enterprise JavaBeans eine Generalüberholung erfahren. Die Version 2.0 der EJB APIs beinhaltet vor allem im Bereich der CMP einige interessante Neuerungen, auf die hier aber nicht weiter eingegangen wird.

 

 

5.2.6 Enterprise Information System Tier

 

Als Enterprise Information System Tier (EIS) bezeichnet die Java™ 2 Plattform ein System, in dem geschäftskritische Daten konsistent abgelegt sind. In der Regel verbirgt sich hinter diesem Terminus eine relationales Datenbank Management System (RDBMS). Gemäß J2EE-Plattform fungiert die Datenbank als „Container“ für die anwendungsrelevanten Daten, d.h. ist ein reiner „Daten-Server“.

 

 


6. Entwicklung einer J2EE Applikation

 

 

6.1 JSP User Interface

 

Die Entwicklung eines User Interface sollte möglichst nach dem Model-View-Controller-Prinzip erfolgen. Dieses Prinzip sieht drei Hauptbestandteile vor, wie man in Abbildung 5 erkennen kann.

 

 

Abbildung 6: Model-View-Controller-Prinzip

 

Auf die Syntax von JSP Seiten soll hier nicht näher eingegangen werden.

 

 

6.1.1 Vor- und Nachteile von JavaServer Pages

 

 

6.1.1.1 Gegenüber ASP

 

Ein sehr großer Vorteil von JSP ist, dass es nicht auf VB oder eine ASP spezifische Sprache beschränkt ist, sondern die volle Vielfalt von Java nutzen kann. Dies macht den Code portable auf andere Systeme und ist nicht auf Windows-Betriebssysteme beschränkt. Dies bedeutet, wenn eine ASP Seite geschrieben wurde, läuft sie nur auf einem Windows Web Server, jedoch nicht auf einem Web-Server, der auf Unix, Linux oder anderen Betriebssystemen läuft.

 

 

6.1.1.2 Gegenüber PHP

 

Der Vorteil gegenüber PHP liegt im Bereich der bereits Vielfältig vorhandenen API’s für das Netzwerk, für Datenbankzugriffe usw.

Ein weiterer Vorteil ist, dass - wenn man Java beherrscht - nicht eine neue Sprache erlernen muss.

 

 

6.1.1.3 Gegenüber Java Script

 

Ein großer Vorteil ist gegenüber Java Script, dass JSP absolut Serverseitig ist. Dies ist auch gleichzeitig ein Nachteil, wenn dynamische Informationen von der Client Umgebung benötigt werden. Ein zusätzlicher Vorteil von JSP ist die Zugreifbarkeit von Datenbanken, Server Informationen etc.

 

6.1.1.4 Nachteil

 

Es muss eine eigene JSP-Engine installiert werden, in dieser gehören dann alle Einstellungen für die einzelnen JSP Seite angeführt.

 

 

6.2 Enterprise JavaBean

 

 

6.2.1 Entwicklung eines Stateless Session Bean

 

Ein Stateless Session Bean (zustandsloses Session Bean) wird in der Regel erstellt, um einen Anwen­dungsfall (Use Case) der Geschäftslogik abzubilden. Ein Session Bean stellt anders als ein Entity Bean kein persistentes Objekt dar. Jeder Client, der Methoden auf einem Session Bean aufruft, erhält vom EJB-Container eine Bean-Instanz zugewiesen. Nach der Methodenabarbeitung wird die Bean-Instanz vom Container wieder in den Bean-Pool zurück gestellt. Im Gegensatz zu einem Stateful Session Bean kann es keinerlei Informationen für den Client transient speichern. Der genaue Unterschied zwischen stateful und stateless Session Beans und deren Auswirkungen auf die Programmierung ist im nächsten Abschnitt erläutert.

 

 

6.2.1.1 Code-Beispiel eines sehr einfachen Stateless Session Bean

 

Remote Interface:

package test;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Hello extends EJBObject
{
//EJB business contract
public String sayHello() throws RemoteException;
}

 

Home Interface:
package test;

import java.rmi.RemoteException;
import javax.ejb.EJBHome;
import javax.ejb.CreateException;

public interface HelloHome extends EJBHome
{
Hello create() throws CreateException, RemoteException;
}

 


Session Bean:
package test;

import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.CreateException;

public class HelloBean implements SessionBean
{

//Businessmethoden. Entspricht dem Remote Interface
public String sayHello() throws RemoteException
{
return "Hello";
}

//EJB technical contract. Muss nicht geändert werden.
public void ejbActivate() throws RemoteException
{}

public void ejbCreate() throws CreateException
{}

public void ejbPassivate() throws RemoteException
{}

public void ejbRemove() throws RemoteException
{}

public void setSessionContext( SessionContext ctx )
throws RemoteException
{}

}//HelloBean

 


Test Client:
package test;

import javax.rmi.PortableRemoteObject;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;

import java.util.Properties;

public class HelloTestClient
{
public static void main( java.lang.String[] args )
{
try
{
//Naming Context initialisieren. Websphere proprietär, da
//hiermit der Name Server von Websphere angesprochen wird.
//Ist bei anderen Application Servern anders.
Properties props = new Properties();
props.put( javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.ejs.ns.jndi.CNInitialContextFactory" );

Context initialContext = new InitialContext( props );

//Lookup des Beans. Name muss mit dem Deployment-Namen
//übereinstimmen inkl. der vollen Packagestruktur
Object objRef = initialContext.lookup( "test/Hello" );

//Home Interface holen
HelloHome home = (HelloHome)
PortableRemoteObject.narrow(objRef, HelloHome.class);

//Remote Interface erzeugen
Hello remote = home.create();

//Businessmethoden auf dem Remote Interface aufrufen
String result = remote.sayHello();
System.out.println( result );

}//try
catch ( Exception e )
{
e.printStackTrace();
}//catch

}//main
}//class

 


Benutzt man einen EJB-Wizard von zum Beispiel IBM Visual Age oder JBuilder, dann generiert dieser das benötigten Klassen und die Methodengerüste. Bei einem Stateless Session Bean muss man keine der ge­nerierten Methoden anfassen. Lediglich die Businessmethoden müssen von Hand erstellt werden. Das Vorgehen zum Er­stellen eines Session Beans wird im folgenden nicht weiter beschrieben, da es im hohen Maße den Rahmen dieser Arbeit sprengt.

 

 

6.2.2 Entwicklung eines Stateful Session Bean

 

Beim Deployment eines Session Beans muss man festlegen, ob es zustandslos („stateless“) oder zu­standsbehaftet („stateful“) sein soll. Ist ein Session Bean state­less, dann wird nach jedem Aufruf einer Businessmethode durch einen Client die Bean-Instanz vom EJB-Container in den Pool aller Session Beans vom gleichen Typ zurück gestellt. Dadurch wird der Aufruf der nächsten Business­methode des gleichen Clients mit sehr hoher Wahrscheinlichkeit von einer anderen Instanz des Session Beans aus­geführt. Das heißt natürlich, dass ein stateless Session Bean keinerlei Informationen für einen speziel­len Client speichern kann. In vielen Fällen ist dies auch gar nicht nötig. Da stateless Session Beans vom Container insgesamt perfor­manter ausgeführt werden können als stateful Session Beans, sollte man in solchen Fällen stets die zustandslose Variante wählen.

 

Manche Anwendungsfälle machen es allerdings nötig, dass ein Client zwei (oder mehrere) Business­methoden eines Session Beans nacheinander aufrufen muss, wobei die zweite Methode Informationen benötigt, die aus der Abarbeitung der ersten Methode resultieren. In diesem Fall muss man das Session Bean als „stateful“ deployen.

 

Stateless

Stateful

Das Bean hat keinen Zustand, d.h. es speichert keinerlei Informationen für den Client zwischen Methodenauf­rufen.

Das Bean hat einen Zustand, d.h. es kann zwischen Methodenaufrufen Informationen für den Client speichern, die in späteren Me­thodenaufrufen verfügbar sind.

Bei jedem Aufruf einer Business­methode erhält der Client vom Container in der Regel eine andere Bean-Instanz

Der Client erhält bei jedem Aufruf einer Businessmethode dieselbe Bean-Instanz vom Container.

Die create-Methode auf dem Home-Interface und die ejbCreate-Methode des Beans haben eine leere Signatur, letztere ist in der Regel leer, d.h. enthält keinen Code.

Die create-Methode auf dem Home Interface und die ejbCreate-Methode des Beans haben als Signatur die Parameter, die für die Initia­lisierung des Zustands benötigt werden. Die ejbCreate-Methode enthält den Code zur Initialisierung des Zustands mit diesen Parametern.

Tabelle 2: Unterschiede zwischen stateless und stateful Session Beans

 

 

6.2.2.1 Code-Beispiel eines einfachen stateful Session Beans

 

Das folgende Beispiel stellt einen Währungsrechner dar. Ein Client kann verschiedene Währungen in Euro umrechnen lassen und umgekehrt. Das Session Bean speichert stets die aktuelle Währung sowie deren Umtauschkurs. Der Client kann nun die Methoden aufrufen, die Währungen umrechnen, und durch Aufruf einer Business­methode die aktuelle Währung ändern. Solange die Währung nicht geän­dert wird, verwendet das Bean stets die gerade gültige.

 

Das Home Interface sieht etwas anders aus. Während die create-Methode bei einem stateless Session Bean eine leere Signatur hat, übergibt man bei einem stateful Session Bean die für die Initialisierung des Zustands nötigen Parameter.

 

import java.util.Hashtable;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
import java.rmi.RemoteException;

public interface CurrencyExchangeHome extends EJBHome
{

//Session Bean wird mit einer Hashtable
//mit den Umtauschkursen initialisiert.
public CurrencyExchange create( Hashtable currencyExchangeTable )
throws CreateException, RemoteException;
}

 

 

Das Remote Interface enthält wie gewohnt die Businessmethoden des Session Beans.

 

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface CurrencyExchange extends EJBObject
{
//Die aktuelle Währung ändern
public void changeCurrentCurrency( String currencyName )
throws RemoteException;

//Den gegebenen Euro-Betrag in die aktuelle Währung umrechnen
public double fromEuro( double amount ) throws RemoteException;

//Den gegebenen Betrag der aktuellen Währung in Euro umrechnen
public double toEuro( double amount ) throws RemoteException;
}

 

 

Das Stateful Session Bean sieht so aus.

import java.rmi.*;
import javax.ejb.*;
import java.util.*;

public class CurrencyExchangeBean implements SessionBean
{
private SessionContext mySessionCtx = null;

//Attribute, die den aktuellen Zustand beschreiben

//Tabelle mit den Umtauschkursen
public Hashtable currencyExchangeTable;

//Aktuelle Währung
public String currentCurrencyName;

//Umtauschkurs der aktuellen Währung
public double currentExchangeRate;

//ejbCreate: Initialisieren des Zustands mit den Anfangswerten

public void ejbCreate( Hashtable currencyExchangeTable )
throws CreateException, RemoteException
{
//Währungstabelle speichern
this.currencyExchangeTable = currencyExchangeTable;

//Währung auf Defaultwert setzen
changeCurrentCurrency( "DEM" );//Default-Währung ist DEM
}
public void ejbActivate() throws RemoteException {}
public void ejbPassivate() throws RemoteException {}
public void ejbRemove() throws RemoteException {}
public SessionContext getSessionContext()
{
return mySessionCtx;
}
public void setSessionContext( SessionContext ctx )
throws RemoteException
{
mySessionCtx = ctx;
}

//Businessmethoden
//Ändern der aktuellen Währung in die angegebene Währung
public void changeCurrentCurrency( String currencyName )
throws RemoteException
{
//Ändern der aktuellen Währung und des aktuellen Umtauschkurses.
this.currentCurrencyName = currencyName;
try
{
double help = getCurrentExchangeRate( currencyName );
if ( help != 0 )
{
this.currentExchangeRate = help;
}
else
{
throw new RemoteException(
"Umtauschkurs ist nicht definiert." );
}
}//try
catch ( Exception e )
{
throw new RemoteException( e.getMessage() );
}//catch
}//method changeCurrentCurrency

//Umrechnung des angegebenen Eurobetrags in die aktuelle Währung
//Der aktuelle Umtauschkurs ist stets angegeben als 1 Euro=1 Währung
//also z.B. 1.95583 bedeutet 1 Euro = 1.95583 DEM.
public double fromEuro( double amount ) throws RemoteException
{
return ( amount * currentExchangeRate );
}

//Umrechnung des angegebenen Betrags in der aktuellen Währung in EURO
//Der aktuelle Umtauschkurs ist stets angegeben als 1 Euro=1 Währung,
//also z.B. 1.95583 bedeutet 1 Euro = 1.95583 DEM.
public double toEuro( double amount ) throws RemoteException
{
return ( amount / currentExchangeRate );
}

//Aktuelle Umtauschrate ermitteln. Dies ist keine Businessmethode,
//sondern eine interne Hilfsmethode
public double getCurrentExchangeRate( String currencyName )
throws Exception
{
double exchangeRate = 0;
try
{
Double exchangeRateDoubleObject
= (Double) currencyExchangeTable.get( currencyName );
if ( exchangeRateDoubleObject != null )
{
exchangeRate = exchangeRateDoubleObject.doubleValue();
}
}//try
catch ( Exception e )
{
throw e;
}//catch
return exchangeRate;
}//method getCurrentExchangeRate
}//class

 

 

Der Client sieht folgendermaßen aus:

import javax.ejb.*;
import java.util.*;
import javax.naming.*;
import javax.rmi.*;

public class CurrencyExchangeTestClient
{
public static void main( String[] args )
{
try
{
//Währungsumtauschtabelle erzeugen:
Hashtable currencyExchangeTable = new Hashtable();
currencyExchangeTable.put( "DEM", new Double( 1.95583 ) );
currencyExchangeTable.put( "ATS", new Double( 14.0 ) );
currencyExchangeTable.put( "FFR", new Double( 6.5 ) );

Properties p = new Properties();
p.put( javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.ejs.ns.jndi.CNInitialContextFactory");
Context initialContext = new InitialContext( p );

//Session Bean Home suchen
Object objRef
= initialContext.lookup( "sessiontest/CurrencyExchange" );
CurrencyExchangeHome home = (CurrencyExchangeHome)
PortableRemoteObject.narrow(
objRef, CurrencyExchangeHome.class );

//Stateful Session Bean erzeugen mit dem initialen Zustand.
CurrencyExchange remote = home.create( currencyExchangeTable );
double calc = remote.fromEuro( 150 );
System.out.println( "150 Euro sind " + calc + " DEM." );
calc = remote.toEuro( 150 );
System.out.println( "150 DEM sind " + calc + " EURO." );

remote.changeCurrentCurrency( "ATS" );
calc = remote.fromEuro( 150 );
System.out.println( "150 Euro sind " + calc + " ATS." );
calc = remote.toEuro( 150 );
System.out.println( "150 ATS sind " + calc + " EURO." );

CurrencyExchange remote2 = home.create(currencyExchangeTable);
remote2.changeCurrentCurrency( "FFR" );
calc = remote2.fromEuro( 150 );
System.out.println( "150 Euro sind " + calc + " FFR." );
calc = remote2.toEuro( 150 );
System.out.println( "150 FFR sind " + calc + " EURO." );

//Wenn man nun erneut eine Methode auf der ersten Session
//Bean-Instanz aufruft, dann stellt man fest, dass hier
//die aktuelle Währung immer noch "ATS" ist.
calc = remote.fromEuro( 150 );
System.out.println( "150 der aktuellen Währung sind "
+ calc + "." );
}//try
catch ( Exception e )
{
e.printStackTrace();
}//catch
}//main
}//Testclient-Klasse

 

 

 

6.2.3 Entwicklung eines Entity Bean

 

Das Entwickeln von Entity Beans ist wesentlich komplizierter als bei Session Beans. Entity Beans stellen persistente Businessobjekte dar, die in einer Datenbank gespeichert werden. Bei Entity Beans unterscheidet man Container Managed Persistence (CMP) und Bean Managed Persistence (BMP). Bei ersterem verwaltet der EJB-Container die Persistenz der Entity Beans. Hierbei muss der Entwickler keinerlei Datenbankzugriffe codieren, da der Container den dafür erforderlichen Code beim Deployment des Entity Beans generiert. Man gibt das Mapping der persistenten Felder des Entity Beans auf das Datenmodell beim Deployment an. Anders bei einem Entity Bean mit BMP; hier kontrolliert das Entity Bean seine Persistenz selbst, d.h. der Entwickler muss alle Datenbankzugriffe mit JDBC von Hand ausprogrammieren. Dies bereitet natürlich mehr Arbeit, andererseits erhält man mehr Flexibilität. Denn CMP funktioniert nur dann, wenn das Entity Bean genau auf eine Tabelle mappt bzw. nur einfache Relationen zwischen Tabellen darstellt. Mappt das Entity Bean hingegen auf kompliziertere Relationen zwischen Tabellen, dann ist CMP meist nicht mehr möglich, und man braucht BMP.

 

Wenn man ein Entity Bean schreiben will, sollte man sich zunächst überlegen, welche persistenten Felder das Entity Bean braucht. Das sind alle Attribute, die in der Datenbank gespeichert werden sollen.

 

 

6.2.3.1 Entwicklung eines Entity Beans mit BMP

 

Ein Entity Bean mit BMP erfordert wesentlich mehr Codierungsaufwand als bei CMP, da alle Daten­bankzugriffe über JDBC von Hand auscodiert werden müssen. Um das Entity Bean schlank zu halten ist es sinnvoll, die Datenbankzugriffe des Entity Beans in einer eigenen Klasse zu kapseln, die dem DataAccessObject (DAO) Design Pattern genügt. Dies ist ein Singleton-Objekt, d.h. für alle Entity Beans vom selben Typ gibt es nur genau eine Instanz des DAO.

 

Methoden, die man laut EJB-Spezifikation auscodieren muss und die vom Container zur Laufzeit auf­gerufen werden sind in Tabelle 3 festgehalten.

 

Methode

Erläuterung

public void ejbActivate() throws RemoteException

Holen des Primary Key aus dem Entity Context; setzen der Attribute, die zum Primary Key gehören.

public <PrimaryKeyClass> ejbCreate( ... )

throws RemoteException

Code zum Einfügen des Entity Beans in die Datenbank. Die Parameter dieser Methode umfassen alle Felder, mit denen das Bean in die Daten­bank geschrieben werden soll. Rückgabewert ist der Primary Key des neu erzeugten Entity Beans. Es kann mehrere ejbCreate-Methoden mit unterschiedlichen Signaturen geben, wenn dies nötig ist, z.B. wenn es mehrere Arten gibt, das Bean in der Datenbank neu anzulegen.

public void ejbPostCreate(...)

throws RemoteException

Zu jeder ejbCreate-Methode muss genau eine ejbPostCreate-Methode mit der gleichen Signatur existieren (Rückgabewert void). Hier kommt der Code hinein, der ausgeführt werden soll nach dem Ein­fügen des Beans in die Datenbank. Kann in der Regel leer bleiben.

public void ejbPassivate()

throws RemoteException

Hier deinitialisiert man die persistenten und nicht persistenten Attri­bute (z.B. die Datenbankverbindungen), d.h. man setzt sie auf NULL.

public void ejbLoad() throws RemoteException

Hier kommt der Code hinein, um die Nicht-Primary Key-Felder des Entity Beans aus der Datenbank auszulesen und zu initialisieren.

public void ejbStore() throws RemoteException

Hier kommt der Code hinein, um das Entity Bean in der Datenbank zu aktualisieren. Der Container ruft diese Methode automatisch auf, wenn sich das Bean geändert hat, d.h. wenn der Client eine Businessmethode aufgerufen hat, die persistente Attribute geändert hat.

public <PrimaryKeyClass> ejbFindByPrimaryKey( <PrimaryKeyClass> key ) throws RemoteException, FinderException

Hier kommt der Code hinein, der nachprüft, ob ein Entity Bean mit dem angegebenen Primary Key in der Datenbank existiert. Wenn ja, wird der gegebene Primary Key zurück gegeben; wenn nein, dann wird eine javax.ejb.FinderException geworfen. Im Home Interface muss eine Methode findByPrimaryKey mit der gleichen Signatur und dem Rückgabewert <RemoteInterface> existieren.

public <PrimaryKeyClass> ejbFindByParam( ... ) throws RemoteException

Zusätzlich zur ejbFindByPrimaryKey-Methode kann man Finder-Methoden mit anderen Suchparametern definieren, z.B. ejbFindBy-Name( String name ). Hier kommt der Code hinein, um eine Such­e mit diesen Parametern in der Datenbank durchzuführen. Wenn maximal ein Suchergebnis möglich ist, dann ist der Rückgabe­wert ebenfalls die PrimaryKey-Klasse, ansonsten eine java.util.Enumeration von Primary Keys Wie man dies reali­siert, findet man in den Code-Beispielen. Alle Finder-Methoden müssen ebenfalls eine Entsprechung im Home Interface haben, d.h. findBy­Param(...) mit Rückgabewert <RemoteInterface> oder Enu­meration.

public void setEntityContext( EntityContext ctx ) throws RemoteException

Initialisieren des EntityContext. Falls DAO-Objekt verwendet wird für die Datenbankzugriffe, dann kann man es hier initialisieren.

public EntityContext getEntityContext() throws RemoteException

Diese Methode ändert man nicht, sondern lässt den generierten Code.

public void unsetEntityContext() throws RemoteException

Der EntityContext wird auf NULL gesetzt. Auch hier kann man den generierten Code stehen lassen.

Tabelle 3: Enitiy Bean Methoden die vom Container zur Laufzeit aufgerufen werden

 

 

6.2.3.1 Code-Beispiel eines Entity-Beans mit BMP

 

Das folgende Beispiel ist ein Entity-Bean, das neben den Standard-Methoden zwei weitere Finder-Methoden enthält. Als Businessmethoden bietet es die Möglichkeit, die Attribute des Entity-Beans (teilweise über das zuge­hörige StateHolder-Objekt) zu lesen und zu ändern.

Das Home Interface enthält eine oder mehrere create-Methoden, die findByPrimaryKey-Methode sowie optional weitere find-Methoden, die alle eine Entsprechung namens ejb. mit der gleichen Signatur in der Entity Bean-Klasse haben müssen.

 

import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.rmi.RemoteException;
import java.util.Enumeration;

public interface KonzernHome extends EJBHome
{

//Mindestens eine create-Methode muss vorhanden sein
public Konzern create( KonzernStateHolder sh )
throws CreateException, RemoteException;

//Die findByPrimaryKey-Methode muss vorhanden sein.
public Konzern findByPrimaryKey(KonzernKey key )
throws RemoteException, FinderException;

//Weitere finder-Methoden sind optional. Diese erwartet höchstens ein Ergebnis:
public Konzern findByKonzernNr( String konzernNr )
throws RemoteException, FinderException;

//Diese finder-Methode kann mehrere Ergebnisse haben:
public Enumeration findByName( String name )
throws RemoteException, FinderException;
}

 

 

Das Remote Interface enthält die Businessmethoden, die in der Entity Bean-Klasse ausprogrammiert sein müssen.

 

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface Konzern extends EJBObject
{
public KonzernStateHolder getStateHolder() throws RemoteException;

public void setStateHolder( KonzernStateHolder sh ) throws RemoteException;

public String getKonzernNummer() throws RemoteException;
}
 

 

Die Primary Key-Klasse ist eine serialisierbare Klasse, die alle Primary Key-Felder als Attribute enthält, sowie einige Methoden haben muss. Benennung ist <Name des Remote Interface>+Key. Standardmäßig haben wir im Datenmodell lediglich NUMBER-Felder als Primary Keys, daher genügt es, einen int-Wert als Attribut der Primary Key-Klasse zu definieren.

 

import java.io.Serializable;

public class KonzernKey implements Serializable
{

//Primary Key-Feld
public int primaryKey;

//Default-Constructor
public KonzernKey() {}

//Eigener Constructor
public KonzernKey( int key )
{
primaryKey = key;
}
//Selbst definierte getter-Methode
public int getPrimaryKey()
{
return primaryKey;
}

//Die equals-Methode ist Pflicht und muss bei einem int-Feld als PK-//Feld so implementiert werden:

public boolean equals ( Object o )
{
if ( o instanceof KonzernKey )
{
KonzernKey oKey = (KonzernKey) o;
if ( this.primaryKey == oKey.getPrimaryKey() )
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}

//Die hashCode-Methode muss so aussehen:
public int hashCode ()
{
return new Integer( primaryKey ).hashCode();
}
}

 

 

Das StateHolder-Objekt des Entity Beans ist eine serialisierbare Klasse, die exakt die persistenten Attribute des Entity Beans mit Ausnahme der Primary Key-Felder, sowie getter- und setter-Methoden für alle (oder einen Teil der) Attribute enthält. Da der Code hierfür sehr einfach ist, wird nur der prinzipielle Aufbau der Klasse dar­gestellt.

 

import java.io.Serializable;

public class KonzernStateHolder implements Serializable
{
//Persistente Attribute des Entity Beans (außer Primary Key-//Felder)
...

//Konstruktor. Wenn das StateHolder-Objekt sehr viele Attribute //hat, dann ist
//es sinnvoll, nur den leeren Defaultkonstruktor zu //implementieren und die
//Attribute generell über die setter-Methoden zu //initialisieren.

public KonzernStateHolder( ... )
{
...
}

//getter-Methoden
...

//setter-Methoden
...
}

 

 

Die Bean-Klasse besteht aus den persistenten Attributen (einschließlich der Primary Key-Felder), getter- und setter-Methoden für die Attribute, den Businessmethoden, den Methodenentsprechungen des Home Interface so­wie den Methoden, die implementiert werden müssen, da das Interface EntityBean implementiert wird.

 

import java.rmi.*;
import javax.ejb.*;
import java.util.*;

public class KonzernBean implements EntityBean
{
private EntityContext entityContext = null;

//DAO-Objekt für die Datenbankzugriffe
private KonzernDAO mDao;

// Persistente Attribute. Wie im StateHolder. Zusätzlich noch Primary Key-Feld(er)
private int id;//Primary key
...//alle anderen Felder

//Getter und Setter für die Attribute.
...

//EJB technical contract
//Methode ejbActivate: Primary Key-Felder initialisieren
public void ejbActivate() throws RemoteException
{
KonzernKey key = (KonzernKey) entityContext.getPrimaryKey();

id = key.getPrimaryKey();
}//method ejbActivate

//Methode ejbPassivate: Entity Bean deinitialisieren
public void ejbPassivate() throws RemoteException
{
mDao = null;//DAO auf NULL setzen
//Alle persistenten Attribute außer Primary Key auf NULL setzen
KonzernStateHolder sh = new KonzernStateHolder();
setStateHolder( sh );
}

//ejbCreate-Methode: Insert des Entity Beans in die Datenbank.
public KonzernKey ejbCreate( KonzernStateHolder sh )
throws CreateException, RemoteException
{
KonzernKey key;
try
{
String konzernNummer = mDao.createNewKonzernNr();
key = mDao.insert( sh );
id = key.getPrimaryKey();
}
catch ( Exception e )
{
throw new CreateException( e.getMessage() );
}
return key;
}

//ejbPost-Create: gleiche Signatur wie ejbCreate
public void ejbPostCreate( KonzernStateHolder sh ) throws RemoteException
{}

//Finder-Methoden

//Methode ejbFindByPrimaryKey: Das Entity Bean in der Datenbank anhand vom Primary
//Key suchen
public KonzernKey ejbFindByPrimaryKey( KonzernKey primaryKey )
throws RemoteException, FinderException
{
try
{
KonzernKey key = mDao.findByPrimaryKey( primaryKey );
if ( key == null )
{
throw new FinderException( "Entity Bean wurde nicht gefunden" );
}
}//try
catch ( Exception e )
{
throw new FinderException( e.getMessage() );
}
return primaryKey;
}//method ejbFindByPrimaryKey

//Methode ejbFindByKonzernNummer. Entspricht der findByKonzernNummer-Methode im
//im Home Interface.
public KonzernKey ejbFindByKonzernNummer( String konzernNr )
throws RemoteException, FinderException
{
try
{
KonzernKey key = mDao.findByKonzernNr( konzernNr );
if ( key == null )
{
throw new FinderException( "Konzern nicht vorhanden" );
}
return key;
}
catch ( Exception e )
{
throw new FinderException( e.getMessage() );
}
}
//Methode ejbFindByName: Suchen von Entity Beans anhand des Attributs “Name“
public Enumeration ejbFindByName( String name )
throws FinderException, RemoteException
{
Enumeration result;
try
{
result = mDao.findByName( name );
}
catch ( Exception e )
{
throw new FinderException( e.getMessage() );
}
return result;
}

//ejbLoad: Nicht-Primary Key-Felder aus der Datenbank lesen und das
//Entity Bean damit initialisieren.
public void ejbLoad() throws RemoteException
{
KonzernKey key = (KonzernKey) entityContext.getPrimaryKey();
KonzernStateHolder sh = null;
try
{
sh = mDao.load( key );
setStateHolder( sh );
id = key.getPrimaryKey();
}//try
catch ( Exception e )
{
throw new RemoteException( e.getMessage() );
}//catch
}//ejbLoad

//Methode ejbRemove: Code zum Löschen des Entity Beans in der //Datenbank
public void ejbRemove() throws RemoteException, RemoveException
{
try
{
mDao.delete( (KonzernKey) entityContext.getPrimaryKey() );
}
catch ( SQLException e )
{
throw new RemoveException( e.getMessage() );
}
}

//Methode ejbStore: Code zum Updaten des Entity Beans in der Datenbank
public void ejbStore() throws RemoteException
{
try
{
KonzernKey key = (KonzernKey) entityContext.getPrimaryKey();
mDao.update( getStateHolder(), key);
}
catch ( Exception e )
{
throw new RemoteException( e.getMessage() );
}
}

//Methode getEntityContext: diese braucht nach der Generierung nicht //verändert zu
//werden.
public EntityContext getEntityContext()
{
return entityContext;
}

//Methode setEntityContext: EntityContext und DAO initialisieren
public void setEntityContext( EntityContext ctx ) throws RemoteException
{
entityContext = ctx;
try
{
mDao = KonzernDAO.getDAO();
}
catch ( Exception e )
{
throw new RemoteException( e.getMessage() );
}
}//method setEntityContext

//Methode unsetEntityContext: diese braucht nach der Generierung //nicht verändert
//zu werden.
public void unsetEntityContext() throws RemoteException
{
entityContext = null;
}

//EJB-Businessmethoden. Entspricht den Businessmethoden im Remote Interface
public KonzernStateHolder getStateHolder() throws RemoteException
{
KonzernStateHolder sh = new KonzernStateHolder();
sh.setName( getName() );
...//usw. für alle Attribute

return sh;
}
public void setStateHolder( KonzernStateHolder sh ) throws RemoteException
{
this.name = sh.getName();
... //usw. für alle Attribute
}
public String getKonzernNummer() throws RemoteException
{
return getKonzernNummer();
}
}

 

 

Das DAO-Objekt enthält Methoden, die über JDBC die entsprechenden Datenbankstatements ausführen.Da die Methoden relativ ähnlich sind im Aufbau (siehe Kapitel über JDBC: Connection holen, Statement erzeugen/vorbereiten, Parameter setzen, Statement ausführen, ResultSet verarbeiten, Statement und Connection schließen, Exception Handling), wird jeweils nur das ausgeführte SQL-Statement angeführt.

 

import java.sql.*;
import java.util.*;
import javax.ejb.*;

public class KonzernDAO
{
public static KonzernDAO mDao;//Das Singleton

//Constructor, empty
public KonzernDAO() {}

//Statische Methode für das Entity Bean, um Singleton-Instanz des //DAO zu holen.
public static KonzernDAO getDAO()
{
if ( mDao == null )
{
mDao = new KonzernDAO();
}
return mDao;
}

//Neuen Primary Key aus Default-Sequence erzeugen
public int createNewPrimaryKey() throws SQLException
{
return createNewPrimaryKey( "GENERAL_SEQ" );
}
//Neuen Primary Key aus angegebener Sequence holen.
public int createNewPrimaryKey( String sequenceName ) throws SQLException
{
int key;
if ( sequenceName == null )
{
sequenceName = "GENERAL_SEQ";
}

String query = "SELECT " + sequenceName + ".NEXTVAL FROM DUAL";
//Select-Statement über JDBC ausführen, Ergebnis = key
...
return key;
}//method createNewPrimaryKey

//Gegenstück zu ejbCreate
public KonzernKey insert( KonzernStateHolder sh ) throws SQLException
{
//Insert-Statement für die KONZERN-Tabelle
public static final String SQL_INSERT_KONZERN
= "INSERT INTO KONZERNE ( ID, NAME, KURZNAME, ALIAS, ORTSNUMMER_BA"
+ ", KONZERNNR, KONZERNART, BEMERKUNG_INTERN, , BEMERKUNG_INTERN "
+ ", BEMERKUNG_EXTERN, KONZERN_GEB_DATUM, DATE_CREATED, USER_CREATED )"
+ " VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SYSDATE, 'Prototype' )";

//Insert-Statement für die ADRESSEN-Tabelle
public static final String SQL_INSERT_ADRESSE
= "INSERT INTO ADRESSEN ( ID, POSTLEITZAHL, ORT, KONZERN, ADRESSART "
+ ", ADRESSART, LAND, BUNDESSTAAT, DATE_CREATED, USER_CREATED )"
+ " VALUES ( GENERAL_SEQ.NEXTVAL, ?, ?, ?, 4, ?, NULL, "
+ ", ADRESSART, ADRESSART SYSDATE, 'Prototype' )" ;

//Create new primary key:
konzernId = createNewPrimaryKey();

//Ausführen des Insert-Statement für die KONZERNE-Tabelle //über JDBC
//Ausführen des Insert-Statement für die ADRESSEN-Tabelle über //JDBC
...

return new KonzernKey( konzernId );
}//method insert

//Gegenstück zu ejbFindByPrimaryKey
public KonzernKey findByPrimaryKey( KonzernKey key ) throws SQLException
{
//Select-Statement für findByPrimaryKey
public static final String SQL_SELECT_PK
= "SELECT ID FROM KONZERNE WHERE ID = ?";
//Select-Statement über JDBC ausführen. Falls etwas gefunden wird, den
//Primary Key zurück geben, sonst NULL zurück geben.
...
return key;
}//method findByPrimaryKey

public String createNewKonzernNr() throws SQLException
{
//Hier Mechanismus zum Erzeugen einer neuen Konzern-Identnummer
//implementieren. Z.B. die maximale vorhandene Nummer selektieren und um
//eins inkrementieren.
String newKonzernNr;
public static final String SQL_SELECT_MAX_KONZERNNR
= "SELECT MAX( TO_NUMBER( KONZERNNR ) ) FROM KONZERNE";
...
return newKonzernNr;
}//method createNewKonzernNr

//Gegenstück zu ejbFindByKonzernNummer
public KonzernKey findByKonzernNr( String konzernNr )
throws SQLException, FinderException
{
KonzernKey key = null;
public static final String SQL_SELECT_KONZERNNR
= "SELECT ID FROM KONZERNE WHERE KONZERNNR = ?";

//SQL-Statement über JDBC ausführen. Falls mehr als ein Ergebnis vorhanden
//ist (darf nicht vorkommen), Exception werfen. Falls kein Ergebnis vorhanden
//ist, NULL zurück geben, ansonsten Ergebnis zurück geben.
return key;
}

//Gegenstück zu ejbFindByName
public Enumeration findByName( String name ) throws Exception
{
Vector vector = new Vector();
Enumeration result;
String likeExpression = "%" + name + "%";
public static final String SQL_SELECT_NAME
= "SELECT ID FROM KONZERNE WHERE NAME LIKE ?";
//SQL-Statement über JDBC ausführen, durch das ResultSet loopen und die
//Ergebnisse als KonzernKey in einen Vector packen:
...
while ( rs.next() )
{
int id = rs.getInt( 1 );
vector.add( new KonzernKey( id ) );
}
...
result = vector.elements();
return result;
}//method findByName

//Gegenstück zu ejbLoad
public KonzernStateHolder load( KonzernKey key ) throws SQLException
{
//SQL-Select-Statement, um alle Nicht-Primary-Key-Felder zu lesen.
public static final String SQL_LOAD
= "SELECT NAME, KURZNAME, ALIAS, ORTSNUMMER_BA, KONZERNART "
+ ", BEMERKUNG_INTERN, BEMERKUNG_EXTERN, KONZERN_GEB_DATUM"
+ ", POSTLEITZAHL, ORT, LAND, BUNDESSTAAT, KONZERNNR "
+ "FROM KONZERNE, ADRESSEN "
+ "WHERE KONZERNE.ID = ADRESSEN.KONZERN "
+ "AND KONZERNE.ID = ?";

KonzernStateHolder sh = new KonzernStateHolder();

//SQL-Statement über JDBC ausführen, den StateHolder mit den Ergebnissen
//füllen.
...
return sh;
}

//Gegenstück zu ejbStore
public void store( KonzernStateHolder sh, KonzernKey key )
throws SQLException
{
//SQL-Update-Statement, um alle änderbaren Felder upzudaten
public static final String SQL_UPDATE_KONZERNE
= "UPDATE KONZERNE SET"
+ " NAME = ?, KURZNAME = ?, ALIAS = ?, ORTSNUMMER_BA = ?,"
+ " KONZERNART = ?, BEMERKUNG_EXTERN = ?, BEMERKUNG_INTERN = ?,"
+ " KONZERN_GEB_DATUM = ?, KONZERNNR = ?"
+ " WHERE ID = ?";
public static final String SQL_UPDATE_ADRESSEN
= "UPDATE ADRESSEN SET"
+ " POSTLEITZAHL = ?, ORT = ?, LAND = ?, BUNDESSTAAT = ?"
+ " WHERE KONZERN = ?";
}

//Gegenstück zu ejbRemove
public void delete( KonzernKey key ) throws SQLException
{
//SQL-Delete-Statement, um Datensatz aus ADRESSEN- und KONZERNE-Tabelle
//zu löschen
public static final String SQL_DELETE_ADRESSEN
= "DELETE FROM ADRESSEN WHERE KONZERN = ?";
public static final String SQL_DELETE_KONZERNE
= "DELETE FROM KONZERNE WHERE ID = ?"
}//method delete
}//class
 

 

How it all fits together – Der Client für das Entity Bean

Die nachfolgenden Code-Beispiele zeigen, wie ein Entity-Bean-Client die typischen Aufgaben, die man mit Entity Beans machen kann, durchführt. Hierzu gehören das Neuanlegen, Ändern, Löschen von Entity Beans und  das Suchen nach Entity Beans in der Datenbank. Prinzipiell muss bei einem Entity Bean wie bei allen EJBs zunächst der initiale Naming-Kontext gesetzt werden und das Home Interface des EJBs gefunden werden. Dies soll natür­lich in einer zentralen Singleton-Klasse gekapselt werden.

 

 

a) Holen des Initialkontexts und Suchen des Home Interface.

    Das funktioniert genau so wie bei Session Beans.

 

import javax.rmi.PortableRemoteObject;
import javax.naming.*;
import java.util.*;

...

//Initialen Naming-Kontext holen
Properties p = new Properties();
p.put( javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.ejs.ns.jndi.CNInitialContextFactory");
Context initialContext = new InitialContext( p );

//Entity Bean im Naming-Kontext suchen
Object objRef = initialContext.lookup( "Konzern" );
KonzernHome home
= (KonzernHome) PortableRemoteObject.narrow( objRef, KonzernHome.class );

 

 

b) Neuanlegen eines Entity Beans

 

//Neuanlegen eines StateHolders für das Entity Bean. Wird mit User-Input gefüllt.
KonzernStateHolder sh= new KonzernStateHolder();
sh.setName( name );
...//usw. für alle anderen Attribute
Konzern remote = home.create( sh );

//Nun kann man die Businessmethoden auf dem Remote Interface aufrufen, z.B. die
//Konzernnummer des gerade neu angelegten Konzerns ermitteln:
String konzernNummer = remote.getKonzernNummer();

 

 

c) Finden eines Entity Beans anhand des Primärschlüssels.

 

KonzernKey key = new KonzernKey( 106 );
try
{
Konzern remote = home.findByPrimaryKey( key );
//Nun kann man die Businessmethoden auf dem Remote Interface aufrufen, z.B.
KonzernStateHolder sh = remote.getStateHolder();
}
catch ( FinderException e )
{
System.out.println( "Nichts gefunden." );
}

 

 

 

d) Finden eines Entity Beans anhand von anderen Suchkriterien
Hierzu ruft man die entsprechende finder-Methode auf dem Home Interface auf. Im ersten Beispiel wird genau ein Ergebnis erwartet. Die finder-Methode liefert dann direkt das Remote Interface auf, und man kann Businessmethoden auf dem Remote Interface aufrufen:

Konzern remote = home.findByKonzernNummer( "011124" );
KonzernStateHolder sh = remote.getStateHolder();

 


Im zweiten Beispiel werden mehrere Ergebnisse erwartet. Hier muss man etwas mehr machen. Die Finder-Methode liefert eine Enumeration von Primary Keys zurück. Um nun Businessmethoden auf den gefundenen Entity Beans aufrufen zu können, muss man diese dann noch mal anhand ihres Primary Keys finden:
 

java.util.Enumeration enum = home.findByName( "Test" );
while ( enum.hasMoreElements() )
{
//Hier Abweichung von der Spezifikation. Eigentlich sollte die Enumeration
//die RemoteInterfaces enthalten.
org.omg.stub.javax.ejb._EJBObject_Stub enumElement
= (org.omg.stub.javax.ejb._EJBObject_Stub) enum.nextElement();
KonzernKey key = (KonzernKey) enumElement.getPrimaryKey();
Konzern remote = home.findByPrimaryKey( key );

KonzernStateHolder sh = remote.getStateHolder();
}

 

 

e) Updaten eines Entity Beans
Zum Updaten eines Entity Beans muss man zunächst das Entity Bean suchen, d.h. auf dem Home Interface eine der Finder-Methoden aufrufen. Dann ruft man auf dem Remote Interface Businessmethoden auf, die Attribute des Entity Beans ändern. Das Entity Bean sollte hierfür geeignete Methoden auf dem Remote Interface zur Verfügung stellen. Der Container ruft dann automatisch die ejbStore-Methode auf, die den Code zum Updaten des Entity Beans in der Datenbank enthält. Möchte man verhindern, dass Clients ein Entity Bean ändern können, dann stellt man einfach keine Businessmethoden auf dem Remote Interface zur Verfügung, die Attribute des Entity Beans ändern, oder man lässt die ejbStore-Methode in der Bean-Klasse leer.
 

//Entity Bean finden
Konzern remote = home.findByPrimaryKey( 106 );

//Attribute lesen und manipulieren
KonzernStateHolder sh = remote.getStateHolder();
sh.setName ( "Neuer Name" );
sh.setKurzname( "Neuer Kurzname" );
sh.setCity ( "Neustadt" );

//Nun Businessmethode aufrufen, die Attribute des Entity Beans ändert.
//Der Rest, also der Aufruf der ejbStore-Methode, macht der Container automatisch.
remote.setStateHolder( sh );

 


f) Löschen eines Entity Beans
Zum Löschen eines Entity Beans braucht man auf dem Home Interface lediglich die remove-Methode mit dem Primary Key des Entity Beans aufzurufen:

KonzernKey key = new KonzernKey( 106 );
home.remove( key );

 

 

6.2.3.2 Entity Bean mit CMP

 

Bei Entity Beans mit Container Managed Persistence muss man erheblich weniger codieren als bei BMP. Im folgenden werden die Unterschiede zu einem Entity Bean mit BMP dargestellt. Während das Home und das Remote Interface und das StateHolder-Objekt sowie die Businessmethoden genau gleich aussehen, unterscheidet sich die Bean-Klasse doch erheblich. Die folgenden Methoden können ganz leer bleiben bzw. so bleiben, wie Visual Age sie beim Anlegen des Beans generiert.

 

Persistente Attribute müssen public sein. Bei BMP dürfen sie auch private sein.

 

 


7. Aussichten

 

Als Microsoft seine .NET-Strategie vorstellte und es sich nach einiger Zeit herum gesprochen hatte, dass sich dahinter mehr verbirgt als nur ein Marketing-Gag, befand sich Sun Microsystems samt dem Java-Lager plötzlich in der Defensive. Es dauerte nicht lange, da wurde Sun ONE geboren – ein als „Vision“ bezeichnetes Bündel von Technologien und Produkten, welches den Kampf gegen .NET aufnehmen sollte.

 

J2EE und generell Sun ONE ist ein Versuch, viele Hersteller für den Kampf gegen Microsoft zu gewinnen. Aber Vorsicht ist hier angesagt. Viele dieser Hersteller wird es in naher Zukunft vielleicht nicht mehr geben, oder sie werden Joint-Ventures eingehen.

Microsoft hat ein gutes Marketingteam, sehr gute Tools, aktive Shared Context Repositories, eine frühere Einführung in Web Services als J2EE, ein einfacheres Programmiermodell und ist an Windows angepasst.

Sun ONE wird von der Industrie unterstützt, erlaubt bereits heute Deployment von Web Services und gibt eine gewisse Plattformneutralität.

Wie man es dreht und wendet:

Microsoft beansprucht ein Monopol, Sun ONE nicht. Jedes Unternehmen kann seine eigene Meinung über die Vor- und Nachteile beider Ansätze bilden und nach Belieben eine Entscheidung treffen. Für mich ist J2EE der Gewinner, weil es mehr Vorteile als .NET hat. Solange es keine .nale Version von .NET gibt (die Entwicklungsumgebung Visual Studio .NET gibt es auch nur in der beta 2), muss man dem zustimmen! Wobei bei Sun ONE einige Verbesserungen von Nöten sind, die aber mit der Einführung neuer APIs kommen werden.

 

 


8. Glossar

API

Application Programming Interface

CORBA

Common Object Request Broker Architecture

CTM

Component Transaction Monitor

DAO

Data Access Objekt

EJB

Enterprise JavaBeans™

GUI

Graphical User Interface

HTML

Hypertext Markup Language

HTTP

Hypertext Transfer Protocol

IP

Internet Protocol

JDBC

Java Database Connectivity

J2EE

Java™ 2 Enterprise Edition

J2SE

Java™ 2 Standard Edition

JSP

JavaServer Pagesä

JVM

Java Virtual Machine

LDAP

Lightweight Directory Access Protocol

NDS

Novell Directory Services

NIS

Network Information Service

ORB

Object Request Broker

PL/SQL

Procedural Language/SQL

RMI

Remote Method Invocation

TCP

Transmission Control Protocol

TPM

Transaction Processing Monitor

XML

Extensible Markup Language

 

 

 

 


9. Quellen

 

 

9.1 Internet

 

http://www.ssw.uni-linz.ac.at

http://www.ideenreich.com

http://www.mysql.com

http://www.jboss.org

http://www.javamagazin.de

http://www.derentwickler.de

http://www.java.sun.com

http://jakarta.apache.org

 

 

9.2 Literatur

 

Java Magazin Ausgabe 11.2001

Java Magazin Ausgabe 12.2001

Java Magazin Ausgabe 03.2002

Professional Java server programming J2EE Edition (Wrox Press 2000)