EJB 2 (Enterprise JavaBeans) mit JBoss und Oracle WebLogic

+ andere TechDocs
+ JMS
+ Sun EJB
+


Java EE (Java Platform, Enterprise Edition) ist eine durch Schnittstellen definierte Architektur für Unternehmensanwendungen, bestehend aus verschiedenen Komponenten.

EJBs (Enterprise JavaBeans) stellen einen wichtigen Bestandteil von Java EE dar. EJBs sind Komponenten, die serverseitig in einem speziellen EJB-Container ablaufen und die vorrangig zur Implementierung von Geschäftslogik und Persistenz verwendet werden.

Die Vorteile von EJBs sind:
+ Standardisierung, Wartungsfreundlichkeit
+ Verteilte Anwendungen möglich
+ Verteilte Transaktionen
+ Definierte Sicherheitsmechanismen
+ Hohe Ausfallsicherheit erreichbar
+ Hohe Skalierbarkeit

Bekannte Open-Source-EJB-Container sind zum Beispiel: JBoss, GlassFish, Apache Geronimo und JOnAS.

Bekannte kommerzielle EJB-Container sind zum Beispiel: Oracle WebLogic Application Server und IBM WebSphere Application Server.

Dieser Text geht auf EJBs in Version 2 ein. In Version 3 gibt es einige Vereinfachungen.



Inhalt

  1. JavaBean versus Enterprise JavaBean
  2. Übersicht zu EJB-Arten
    1. Session Bean (Stateless, Stateful)
    2. Entity Bean (BMP, CMP)
    3. MDB (Message Driven Bean) (Queue, Topic)
  3. Vorbemerkung zu Enterprise JavaBeans
  4. Installation von Oracle WebLogic
  5. Installation von JBoss
  6. Einrichtung einer Data Source zu einer MySQL-Datenbank
  7. Gemeinsame Dateien für die Beispiele
    1. Projektverzeichnis, Lib, build.properties, Ant build.xml, JNDI, Log4j
  8. Beispiel: Stateful Session Bean
    1. EJB Home Interface
    2. EJB Remote Interface
    3. Implementierung
    4. EJB Deployment-Descriptor
    5. Oracle WebLogic: Zusätzlicher Deployment-Descriptor
    6. Client-Anwendung
    7. Bemerkungen
    8. Eclipse
  9. Beispiel: CMP Entity Bean
    1. EJB Home Interface
    2. EJB Remote Interface
    3. Implementierung
    4. Standard EJB Deployment-Descriptor
    5. JBoss CMP-JDBC-Deployment-Descriptor
    6. Oracle WebLogic EJB- und CMP-RDBMS-Deployment-Descriptor
    7. Client-Anwendung
    8. Bemerkungen
  10. Beispiel: MDB (Message Driven Bean)
    1. Implementierung
    2. JMS-Topic-Service
    3. Standard EJB Deployment-Descriptor
    4. JBoss MDB Deployment-Descriptor
    5. JMS-Nachrichtensender
  11. Links auf weiterführende Informationen


JavaBean versus Enterprise JavaBean

  JavaBeans Enterprise JavaBeans
Definiert in Java SE (Java Standard Edition) Java EE (Java Enterprise Edition)
Laufzeitumgebung Kommen ohne Container-Umgebung aus und können in nahezu allen Java-Umgebungen verwendet werden (auch in Standalone-Programmen) Benötigen speziellen EJB-Container (in Java EE Application Servern)
Interfaces, Methoden Einfache Java-Klassen mit 'private' Attributen sowie Getter- und Setter-Methoden (POJO, Plain Old Java Objects) Genaue Interface-Spezifikationen ('Home', 'Remote' und '...Bean' Interface bei Session und Entity Bean sowie 'MessageDrivenBean' Interface bei Message Driven Bean)
GUI Können sowohl sichtbare GUI-Komponenten als auch interne Backend-Komponenten sein Können niemals sichtbare GUI-Komponente sein
Restriktionen Nahezu keine Restriktionen kein AWT, kein Zugriff auf Dateien per 'java.io', keine eigenen Threads, keine Native-Aufrufe, nur serialisierbare Parameterobjekte
Kommunikation Beliebig Vorwiegend per RMI, RMI-IIOP, JMS, JCA
Persistenz Beliebig,
z.B. direkte SQL-Kommandos per JDBC
oder über Java-Objekte per O/R Mapping (z.B. Hibernate oder JDO)
Normalerweise in Entity Beans,
entweder 'Bean Managed Persistence' (BMP) (sowohl SQL/JDBC als auch O/R Mapping)
oder 'Container Managed Persistence' (CMP)
Standardisierung,
Wartungsfreundlichkeit,
verteilte Anwendungen,
verteilte Transaktionen,
Sicherheitsmechanismen,
Skalierbarkeit,
Clusterfähigkeit
Muss programmiert werden Wird vom EJB-Container angeboten
Einsatz Beliebig, z.B. in Webapplikationen zusammen mit JSP (z.B. auf Tomcat) In großen Unternehmensanwendungen (auch Webapplikationen)
Anfänglicher Lernaufwand Niedrig Hoch

Weitere Informationen zu Restriktionen für EJBs finden Sie unter: http://java.sun.com/blueprints/qanda/ejb_tier/restrictions.html.



Übersicht zu EJB-Arten

Java-EE-Struktur
Eine Java-EE-Webanwendungen kann typischerweise etwa folgenden vereinfachten Aufbau haben (Genaueres hierzu finden Sie unter sw-patterns.htm#JavaEE-Patterns):

                           Client    
                              |
                              |HTTP
............................. | .............................
.                             |                Web Container.
.                     Front Controller                      .
.                             |                             .
.                     Business Delegate                     .
............................. | .............................
                              |RMI-IIOP
............................. | .............................
.                             |                EJB Container.
.               Session Facade (Session EJB)                .
.         ____________________|____________________         .
.        |             |             |             |        .
.   Entity EJB    Entity EJB    Entity EJB       MDB       .
.        |             |             |             |        .
........ | ........... | ........... | ........... | ........
         |             |             |             |
          SQL-Datenbank        ERP, Legacy       JMS    


Session Bean
Über Session Beans erfolgt die Kommunikation mit Komponenten außerhalb des EJB-Containers, meistens mit der Präsentationsschicht (z.B. JSP, Servlets), über welche Clients zugreifen. Dabei kann die Session Bean als 'Facade' dienen.
Anders als eine Entity Bean bedient eine Session Bean nur einen Client gleichzeitig, konkurrierende Clients kommunizieren mit anderen Session-Bean-Instanzen.
Es wird unterschieden zwischen Stateless Session Beans und Stateful Session Beans.
Stateless Session Beans
Stateless Session Beans dienen als 'Service Component' und haben keinen eigenen internen 'Zustand', den sie sich zwischen Methodenaufrufen merken. Nacheinanderfolgende Methodenaufrufe eines Clients werden eventuell von verschiedenen Stateless-Session-Bean-Instanzen behandelt. Stateless Session Beans sind zu bevorzugen, da sie performanter, leichter vom Container zu verwalten und im Java EE Application Server besser skalierbar sind.
Stateful Session Beans
Stateful Session Beans dienen als 'Session Component' und können sich einen eigenen 'Zustand' zwischen Methodenaufrufen merken. Sie sind deshalb weniger performant und schwieriger zu verwalten. Zum Beispiel könnte ein Warenkorb per Stateful Session Bean realisiert werden.
EJBs unterstützen Transaktionen. Einige Transaktionsarten können von Entity Beans oder von Clients außerhalb des EJB-Containers verwaltet werden. Aber meistens werden Transaktionen von Session Beans verwaltet. Nach der Art der Transaktionsverwaltung wird unterschieden:
BMT (Bean Managed Transactions)
CMT (Container Managed Transactions)
Weiteres zu BMT, CMT und Transaktionen finden Sie in jee-transaktionen.htm.
Entity Bean
Die Hauptaufgabe der Entity Beans als 'Persistence Components' ist die Persistenz, also das dauerhafte Speichern über mehrere Sitzungen hinweg (meistens in relationalen Datenbanken).
Entity Beans repräsentieren oft ein Objekt aus der realen Welt (in objektorientierter Darstellung).
Die Daten einer Entity Bean entsprechen einer Zeile einer Datenbanktabelle (oder mehrerer Tabellen). Normalerweise können auch nur Operationen an genau einem Datensatz (also einer Tabellenzeile) durchgeführt werden (anders als bei SQL, wo über eine 'where'-Bedingung viele Tabellenzeilen auf einmal manipuliert werden können).
Anders als eine Session Bean kann eine Entity Bean mehrere Clients gleichzeitig bedienen (dabei sind Clients in der Regel Session Beans).
Es wird unterschieden zwischen:
BMP (Bean Managed Persistence)
Bei Bean Managed Persistence muss der Programmierer die Persistenz realisieren, zum Beispiel über direkte SQL-Kommandos per JDBC-Zugriff oder über Java-Objekte über ein O/R Mapping Framework (z.B. Hibernate oder JDO).
CMP (Container Managed Persistence)
Der Container bewerkstelligt die Persistenz. Das Laden und Speichern (z.B. von und zur Datenbank) erfolgen automatisch.
MDB (Message Driven Bean)
Die Message Driven Bean kann nur auf Grund eingehender JMS-Nachrichten aktiv werden. Das JMS-API (Java Message Service) beschreibt Server-Dienste zur Verwaltung asynchroner Nachrichten.
Beim Java Message Service wird unterschieden zwischen:
JMS Queue (Point-to-Point)
Nachrichten werden im JMS Server in einer Nachrichtenschlange (Queue) zwischengespeichert. Nachrichten sind normalerweise für nur einen Konsumenten bestimmt. Nach Abholung befindet sich die Nachricht nicht mehr im JMS Server.
JMS Topic (Point-to-Multipoint, Publish/Subsribe, pub-sub, Publizieren/Abonnieren)
Nachrichten beziehen sich auf ein Thema (Topic) und sind normalerweise für viele Konsumenten bestimmt, die sich als Abonnent registrieren müssen. Auch nach Abholung bleibt die Nachricht weiterhin im JMS Server, damit auch andere Konsumenten die Nachricht lesen können. Nach einer bestimmten Zeit kann die Nachricht auf dem JMS Server gelöscht werden (und wird häufig durch eine aktuellere Nachricht ersetzt, z.B. Börsen-Ticker).


Vorbemerkung zu Enterprise JavaBeans

Enterprise JavaBeans bestehen aus mehreren Java-Klassen und Konfigurationsdateien (z.B. XML-Deployment-Descriptoren). Bei Session und Entity Beans sind es mindestens folgende drei Java-Klassen:

Es darf nicht vergessen werden, dass der Client nie direkt mit der EJB-Implementierung interagiert. Die Kommunikation erfolgt per RMI (bzw. RMI-IIOP), die Skeletons und Stubs werden automatisch erzeugt. Das von der 'create()'-Methode im 'Home Interface' returnierte Objekt ist ein clientseitiges Objekt, welches das 'Remote Interface' implementiert, aber es ist nicht das serverseitige Objekt der Bean-Implementierung (wenn es auch damit kommuniziert).

Wenn eine EJB eine Referenz auf eine andere EJB benötigt, sollte nicht direkt nach dem Bean-Namen oder dem Namen des Home oder Remote Interfaces gesucht werden, sondern nach dem Namen '"java:comp/env/ejb/MeinEjbJndiName"'. Die gesamte Sequenz zur Erlangung der Bean-Referenz lautet:
Context ctx = new InitialContext();
Object ref = ctx.lookup("java:comp/env/ejb/MeinEjbJndiName");
IMeinEjbNameHome home = (IMeinEjbNameHome)PortableRemoteObject.narrow(ref,IMeinEjbNameHome.class);
IMeinEjbName meinEjbName = home.create();

Das Initialisieren des 'InitialContext', der 'lookup()' im 'InitialContext' und der 'PortableRemoteObject.narrow()'-Aufruf können mehrere Sekunden dauern. Damit diese Vorgänge nur einmal stattfinden, sollten die 'EJBHome'-Objekte in einem so genannten Service Locator gecacht werden. Siehe hierzu: jee-jndi.htm#ServiceLocator.

Wenn eine Bean eine Referenz auf sich selbst einer anderen Bean übergeben will, darf sie dazu nicht das Schlüsselwort 'this' verwenden. Richtig ist stattdessen 'sessionContext.getEJBObject()' (bzw. 'entityContext.getEJBObject()') (in der Regel mit vorangestelltem Typecast auf den Typ des Objekts). So werden Probleme mit dem in der Bean-Implementierung nicht implementiertem Remote Interface vermieden. Auch benötigen Sie wieder die Methode 'PortableRemoteObject.narrow()'.

Ebenfalls nicht vergessen werden darf, dass der EJB-Container die Lebensdauer der EJBs bestimmt und nicht unbedingt Client-Aufrufe die Zeitpunkte des Erzeugens und Entfernens definieren. EJBs werden eventuell in Pools vorgehalten und bei Bedarf vom Container aktiviert. Selbst den Zeitpunkt des Schreibens in die Datenbank bestimmt bei CMP der Container.

'setSessionContext()' wird am Anfang genau einmal aufgerufen. Aber machen Sie keine Annahmen über die Zeitpunkte und Reihenfolgen der Container-Methodenaufrufe 'ejbActivate()' und 'ejbPassivate()'. Ob und wann diese Methoden aufgerufen werden, bestimmt der Container. Zudem ist das Verhalten bei Session Beans (üblicherweise Pooling) anders als bei Entity Beans (üblicherweise Passivating).

Die Namen der eigenen Methoden dürfen nicht mit 'ejb' beginnen. Eine 'finalize()'-Methode ist nicht erlaubt.

Verlassen Sie sich nicht darauf, dass Parameter stets remote und deshalb 'per Value' übertragen werden. Laufen kommunizierende Beans in derselben JVM, können lokale Zugriffe mit Parameterzugriffen 'per Referenz' konfiguriert werden, so dass die aufgerufene Bean das Originalobjekt des Aufrufers ändern kann.

Denken Sie daran, dass Ihre EJB-Anwendung eventuell in einem Cluster auf mehrere Application Server verteilt wird und parallel mehrfach läuft. Falls Sie 'globale' Variablen benötigen, realisieren Sie dies über die Datenbank oder über das JNDI. Vermeiden Sie möglichst Singletons und statische nicht-final Variablen, bzw. gehen Sie zumindest nicht davon aus, dass statische Variablen und Singletons auf geclusterten Servern in allen VMs den gleichen Wert haben. Falls Sie doch statische Variablen verwenden oder Objekte mehreren Threads zur Verfügung stellen, müssen Sie eventuell Codeabschnitte per 'synchronized' schützen, was allerdings Performance kostet. Weiteres hierzu finden Sie unter http://www.roseindia.net/javatutorials/J2EE_singleton_pattern.shtml, http://java.sun.com/developer/technicalArticles/Programming/singletons und java-concurrency.htm.

In den folgenden Beispielen werden nur Grundlagen erörtert. Dabei werden zum besseren Verständnis alle benötigten Dateien manuell erstellt. In der praktischen Entwicklung wird häufig ein Teil der Arbeit Codegeneratoren überlassen. Bis Java 1.4 und EJB2 empfiehlt sich der Einsatz von XDoclets, ab Java 5 und EJB3 sind die Java-Annotations vorzuziehen.



Installation von Oracle WebLogic

... siehe: jee-oracleweblogic.htm#Installation.



Installation von JBoss 4.x

Die folgenden Beispiele sind alle mit JBoss als Java EE Application Server lauffähig.
Zu den ersten Beispielen ist auch die Inbetriebnahme unter Oracle WebLogic beschrieben. Für andere Java EE Application Server müssen die Beispiele angepasst werden.

  1. Installieren Sie ein aktuelles Java SE JDK und Ant, zum Beispiel wie beschrieben unter java-install.htm (inklusive Setzen der korrekten Umgebungsvariablen).
  2. Download des JBoss-Archives (z.B. 'jboss-4.0.4.GA.zip') von http://www.jboss.org/jbossas.
  3. Entpacken des Archives und kopieren des Verzeichnisbaums in ein Verzeichnis Ihrer Wahl. Wahlweise Umbenennen des beim Entpacken erzeugten Verzeichnisnamens mit Versionsnummer zum Beispiel zu 'JBoss' ohne Versionsnummer.
    Bitte beachten: Der Pfad darf keine Leerzeichen enthalten (wie z.B. bei 'Program Files').
  4. Setzen der Umgebungsvariablen 'JAVA_HOME' auf das Java-Verzeichnis (z.B. 'C:\Program Files\Java\jdk1.6') und 'JBOSS_HOME' auf das JBoss-Verzeichnis (z.B. 'C:\JBoss')
    (unter Windows 7 und Vista: 'WindowsTaste + PauseTaste' | 'Erweiterte Systemeinstellungen' | Reiter 'Erweitert' | 'Umgebungsvariablen...'; unter Windows XP unter 'Start' | rechter Mausklick auf 'Arbeitsplatz' | 'Eigenschaften' | 'Erweitert' | 'Umgebungsvariablen'; unter Linux siehe linux.htm#Umgebungsvariablen).
  5. JBoss wird gestartet über 'run.bat' und heruntergefahren über 'shutdown.bat -S' (bzw. unter Linux mit 'run.sh'/'shutdown.sh').
  6. Unter http://localhost:8080 erhalten Sie eine erste JBoss-Webseite.
    Unter http://localhost:8080/web-console öffnet sich die JBoss Management Console.
    Unter http://localhost:8080/jmx-console öffnet sich die JBoss JMX Agent View.
  7. Unter http://docs.jboss.org/jbossas/getting_started/v4/html finden Sie eine 'Getting Started'-Doku.
    Unter http://www.jboss.org/docs/index#as finden Sie weitere Doku zu JBoss.


Einrichtung einer Data Source zu einer MySQL-Datenbank

  1. Oracle WebLogic

    Falls Sie Oracle WebLogic einsetzen: Installieren Sie die Data Source wie beschrieben unter: jee-oracleweblogic.htm#DataSource-MySQL.

  2. JBoss

    Falls Sie JBoss einsetzen: Installieren Sie die Data Source wie im Folgenden beschrieben wird.

  3. MySQL

    Falls Sie noch keine MySQL-Datenbank installiert haben: Installieren Sie MySQL zum Beispiel wie beschrieben unter: mysql.htm#InstallationUnterWindows. Die folgende Beschreibung geht davon aus, dass Sie als 'Database-Name' "MeineDb" wählen ("CREATE DATABASE MeineDb;") (Sie können auch stattdessen die 'connection-url' im Connector Service anpassen).

  4. MySQL verwendet defaultmäßig die 'MyISAM'-Engine, die aber keine Transaktionen unterstützt. Wenn Sie Transaktionsunterstützung wollen, können Sie die 'InnoDB'-Engine verwenden: Öffnen Sie die zu MySQL gehörende 'my.ini'- oder 'my.cnf'-Datei, zum Beispiel in den Verzeichnissen '/etc/mysql', 'C:\', 'C:\Windows' oder 'C:\Programme\MySQL\MySQL Server 4.1'. Stellen Sie sicher dass nicht 'skip-innodb' gesetzt ist und kontrollieren Sie die anderen 'InnoDB Specific options' (z.B. 'innodb_data_file_path=ibdata:30M').

  5. Downloaden Sie den zur Datenbank passenden JDBC-Treiber, für MySQL zum Beispiel 'mysql-connector-java-5.1.16.zip'. Entpacken Sie die .zip-Datei und kopieren Sie die darin enthaltene Treiberdatei 'mysql-connector-java-5.1.16-bin.jar' in das zu Ihrem JBoss-Server gehörende lib-Verzeichnis, z.B. nach: '<JBOSS_HOME>\server\default\lib'.

  6. JBoss DataSource Connector Service

    Registrieren Sie in JBoss (bei gestopptem JBoss) einen geeigneten DataSource Connector Service, indem Sie aus dem '<JBOSS_HOME>\docs\examples\jca'-Verzeichnis die zur Datenbank passende '...-ds.xml'-Datei, also für die MySQL-Datenbank die 'mysql-ds.xml'-Datei, in das zu Ihrem JBoss-Server gehörende deploy-Verzeichnis, also zum Beispiel in das '<JBOSS_HOME>\server\default\deploy'-Verzeichnis, kopieren und zum Beispiel folgendermaßen anpassen:

    <?xml version="1.0" encoding="UTF-8"?>
    <datasources>
      <local-tx-datasource>
        <jndi-name>MeinDatasourceJndiName</jndi-name>
        <connection-url>jdbc:mysql://localhost:3306/MeineDb</connection-url>
        <driver-class>com.mysql.jdbc.Driver</driver-class>
        <user-name>root</user-name>
        <password>mysqlpwd</password>
        <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
        <metadata>
          <type-mapping>mySQL</type-mapping>
        </metadata>
      </local-tx-datasource>
    </datasources>
    

    Passen Sie die Einträge an Ihre Datenbankverbindung an.



Gemeinsame Dateien für die Beispiele

  1. Projektverzeichnis

    Legen Sie ein Projektverzeichnis an, z.B. 'D:\MeinWorkspace\MeinEjb2Projekt' (im Folgenden '<MeinEjb2Projekt>' genannt) und darin das Unterverzeichnis 'lib'.

  2. AppServer-Client-Lib

    Falls es zu Ihrem Java EE Application Server Client-.jar-Libs gibt, kopieren Sie diese in das Unterverzeichnis '<MeinEjb2Projekt>\lib', beispielsweise:

    für App-Server Client-.jar-Lib aus Verzeichnis
    JBoss 4.0: jbossall-client.jar JBoss\client
    GlassFish V2: appserv-deployment-client.jar,
    appserv-ext.jar, appserv-rt.jar,
    javaee.jar
    glassfish\lib

    Für einige andere Java EE Application Server muss stattdessen entweder eine spezielle Client-.jar-Lib erstellt werden oder eine bestimmte .jar-Lib aus dem AppServer-Verzeichnis dem Classpath hinzugefügt werden (siehe hierzu auch jee-jndi.htm#AppClient-Lib).

  3. EJB-API

    Falls Sie zu Ihrem Java EE Application Server keine Client-.jar-Lib kopiert haben, müssen Sie zumindest eine das EJB-API enthaltende .jar-Lib in das Unterverzeichnis '<MeinEjb2Projekt>\lib' kopieren, damit Sie kompilieren können, beispielsweise für WebLogic 10.3.4:

    für App-Server EJB-API-.jar-Lib aus Verzeichnis
    WebLogic 10.3.4: javax.ejb_3.0.1.jar WebLogic\modules
  4. JNDI

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' eine für den verwendeten Java EE Application Server geeignete 'jndi.properties'-Datei (wird von 'InitialContext()' benötigt) (siehe hierzu auch jee-jndi.htm#jndi.properties) (passen Sie den 'java.naming.provider.url'-Eintrag an die Adresse Ihres Java EE Application Servers an). Beispiele:

    Für JBoss:

    java.naming.provider.url=jnp://localhost:1099
    java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
    java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
    jnp.socket.Factory=org.jnp.interfaces.TimedSocketFactory
    

    Für Oracle WebLogic:

    java.naming.provider.url=t3://localhost:7001
    java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
    

    Für GlassFish V2:

    org.omg.CORBA.ORBInitialHost=localhost
    org.omg.CORBA.ORBInitialPort=3700
    java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
    java.naming.factory.url.pkgs=com.sun.enterprise.naming
    java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
    

    Mit Anmelde-Account:

    java.naming.provider.url=...
    java.naming.factory.initial=...
    java.naming.security.principal=meinAppSrvBenutzername
    java.naming.security.credentials=meinAppSrvKennwort
    
  5. build.properties

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' eine Properties-Datei 'build.properties'.
    Falls Ihr Java EE Application Server eine spezielle .jar-Lib für den Client benötigt, geben Sie diese an.
    Falls Ihr Java EE Application Server für Auto-Deployment konfiguriert ist, geben Sie das Auto-Deployment-Verzeichnis an.

    Für JBoss zum Beispiel so:

    deploy.dir=C:/JBoss/server/default/deploy
    

    Für Oracle WebLogic zum Beispiel so:

    deploy.dir=C:/WebLogic/user_projects/domains/MeineDomain/autodeploy
    lib.clnt.file=C:/WebLogic/wlserver_10.3/server/lib/weblogic.jar
    

    Für GlassFish V2 zum Beispiel so:

    deploy.dir=C:/GlassFish/glassfish/domains/domain1/autodeploy
    

    (Passen Sie die Pfade an Ihre Installation an.)

  6. Ant build.xml

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' die folgende Datei 'build.xml':

    <?xml version="1.0"?>
    <project name="My Java EE Application Server Application" default="run-client">
    
      <!-- Set the following three properties at command line, e.g.:
           ant -Dclntapp=myclientpkg.MyClientApp -Dsrvjar=MyServerApp.jar -Dsrvpkg=myserverpackage
      <property name="clntapp"       value="..." />
      <property name="srvjar"        value="..." />
      <property name="srvpkg"        value="..." />
      -->
      <!-- Overwrite some of the following props in a 'build.properties' file:
      <property name="deploy.dir"    value="..." />
      <property name="lib.clnt.file" value="..." />
      -->
      <property file="build.properties" />
      <property name="deploy.dir"    value="dist" />
      <property name="dist.dir"      value="dist" />
      <property name="libs.dir"      value="lib" />
      <property name="lib.clnt.file" value="" />
      <property name="classes.dir"   value="bin" />
      <property name="src.dir"       value="src" />
    
      <target name="clean">
        <delete dir="${dist.dir}" />
        <delete dir="${classes.dir}" />
      </target>
    
      <target name="compile">
        <mkdir dir="${classes.dir}" />
        <javac srcdir="${src.dir}" destdir="${classes.dir}" debug="true" deprecation="true">
          <classpath>
            <fileset dir="${libs.dir}" includes="**/*.jar" />
          </classpath>
        </javac>
      </target>
    
      <target name="create-jar" depends="compile">
        <mkdir dir="${dist.dir}" />
        <jar jarfile="${dist.dir}/${srvjar}">
          <metainf dir="${src.dir}/${srvpkg}" includes="**/*.xml" />
          <fileset dir="${classes.dir}" includes="${srvpkg}/**/*.class" />
        </jar>
      </target>
    
      <target name="deploy" depends="create-jar">
        <copy todir="${deploy.dir}">
          <fileset dir="${src.dir}/${srvpkg}" includes="*-service.xml" />
        </copy>
        <copy todir="${deploy.dir}" file="${dist.dir}/${srvjar}" />
      </target>
    
      <target name="undeploy">
        <delete file="${deploy.dir}/${srvjar}" />
      </target>
    
      <target name="run-client" depends="deploy">
        <sleep seconds="7" />
        <java fork="true" classname="${clntapp}">
          <classpath>
            <fileset dir="${libs.dir}" includes="**/*.jar" />
            <pathelement path="." />
            <pathelement path="${classes.dir}" />
            <pathelement path="${env.CLASSPATH}" />
            <pathelement path="${lib.clnt.file}" />
          </classpath>
        </java>
      </target>
    
    </project>
    

    Dokumentation zur Ant-Syntax finden Sie unter http://ant.apache.org/manual/.

  7. Log4j

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' die folgende Datei 'log4j.properties' (weiteres zu Log4j finden Sie unter java-log4j.htm):

    log4j.rootCategory=DEBUG, CONSOLE
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.Threshold=INFO
    log4j.appender.CONSOLE.Target=System.out
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c{1}] %m%n
    


Beispiel: Stateful Session Bean

  1. Dieses Beispiel setzt voraus, dass die oben aufgeführten "Gemeinsamen Dateien für die Beispiele" vorbereitet sind, sowie dass JBoss, Oracle WebLogic oder GlassFish installiert ist. Für andere Java EE Application Server müsste es angepasst werden.

  2. Sie können die meisten der im Folgenden vorgestellten Dateien auch downloaden (außer den Libs).

  3. Legen Sie in Ihrem Projektverzeichnis '<MeinEjb2Projekt>' das Unterverzeichnis 'src' und darin die Unterverzeichnisse 'src\meinclient' und 'src\meinsessionbeanpkg' an:

    [\MeinWorkspace]
      '- [MeinEjb2Projekt]
           |- [lib]
           |    '- ... [z.B. jbossall-client.jar]
           |- [src]
           |    |- [meinclient]
           |    '- [meinsessionbeanpkg]
           |- build.properties
           |- build.xml
           |- jndi.properties
           '- log4j.properties
    
  4. EJB Home Interface

    Über die 'create()'-Methode[n] im 'Home Interface' bekommt der Client Zugriff auf ein Interface zum (entfernten) EJB-Objekt auf dem Server, dem 'EJB Remote Interface'.

    Anders als Stateless Session Beans dürfen Stateful Session Beans mehrere 'create()'-Methoden implementieren.

    Anders als in Entity Beans existieren bei Session Beans normalerweise keine zusätzlichen Methoden zum Finden ('findBy...()').

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'IHelloWorldHome.java':

    package meinsessionbeanpkg;
    
    import java.rmi.RemoteException;
    import javax.ejb.*;
    
    public interface IHelloWorldHome extends EJBHome
    {
      public IHelloWorld create() throws RemoteException, CreateException;
      public IHelloWorld create( String myname ) throws RemoteException, CreateException;
    }
    
  5. EJB Remote Interface

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'IHelloWorld.java':

    package meinsessionbeanpkg;
    
    import java.rmi.RemoteException;
    import javax.ejb.EJBObject;
    
    public interface IHelloWorld extends EJBObject
    {
      String  sayHello() throws RemoteException;
      Integer getCount() throws RemoteException;
    }
    
  6. Implementierung der Stateful Session Bean

    In der Bean-Implementierung müssen alle im Remote Interface deklarierten Methoden implementiert werden und es muss zu jeder im Home Interface definierten 'create()'-Methode das passende Gegenstück mit den selben Parametern als 'ejbCreate()'-Methode geben. Bitte beachten Sie, dass die im Home Interface deklarierten 'create()'-Methoden das Remote Interface returnieren, während die 'ejbCreate()'-Implementierungen 'void' returnieren.

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'HelloWorldImpl.java':

    package meinsessionbeanpkg;
    
    import javax.ejb.*;
    
    public class HelloWorldImpl implements SessionBean
    {
      private SessionContext ctx = null;
      private String hello = null;
      private int count = 0;
    
      public void ejbCreate()
      {
        hello = "Hallo Welt";
      }
    
      public void ejbCreate( String myname )
      {
        if( null == myname ) myname = "Welt";
        hello = "Hallo " + myname;
      }
    
      public String sayHello()
      {
        count++;
        return hello;
      }
    
      public Integer getCount()
      {
        return new Integer( count );
      }
    
      public void ejbActivate() {}
      public void ejbPassivate() {}
      public void ejbRemove() {}
    
      public void setSessionContext( SessionContext sessionContext )
      {
        ctx = sessionContext;
      }
    }
    
  7. EJB Deployment-Descriptor

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'ejb-jar.xml':

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE ejb-jar PUBLIC
      "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
      "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
    <ejb-jar>
      <enterprise-beans>
        <session>
          <ejb-name> HelloWorld </ejb-name>
          <home> meinsessionbeanpkg.IHelloWorldHome </home>
          <remote> meinsessionbeanpkg.IHelloWorld </remote>
          <ejb-class> meinsessionbeanpkg.HelloWorldImpl </ejb-class>
          <session-type> Stateful </session-type>
          <transaction-type> Container </transaction-type>
        </session>
      </enterprise-beans>
      <assembly-descriptor>
        <container-transaction>
          <method>
            <ejb-name> HelloWorld </ejb-name>
            <method-name> * </method-name>
          </method>
          <trans-attribute> Supports </trans-attribute>
        </container-transaction>
      </assembly-descriptor>
    </ejb-jar>
    
  8. Oracle WebLogic: Zusätzlicher Deployment-Descriptor

    Einige Java EE Application Server benötigen eine weitere Beschreibungsdatei.
    JBoss und GlassFish V2 benötigen sie nicht, aber Oracle WebLogic benötigt sie zum Beispiel.

    Falls Sie WebLogic ab Version 9.x einsetzen:
    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'weblogic-ejb-jar.xml':

    <?xml version="1.0" encoding="UTF-8"?>
    <weblogic-ejb-jar xmlns="http://www.bea.com/ns/weblogic/90"
                      xmlns:j2ee="http://java.sun.com/xml/ns/j2ee"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xsi:schemaLocation="http://www.bea.com/ns/weblogic/90
                                          http://www.bea.com/ns/weblogic/90/weblogic-ejb-jar.xsd">
      <weblogic-enterprise-bean>
        <ejb-name> HelloWorld </ejb-name>
        <stateful-session-descriptor>
        </stateful-session-descriptor>
        <transaction-descriptor>
          <trans-timeout-seconds>30</trans-timeout-seconds>
        </transaction-descriptor>
        <enable-call-by-reference>true</enable-call-by-reference>
        <jndi-name> HelloWorld </jndi-name>
      </weblogic-enterprise-bean>
    </weblogic-ejb-jar>
    

    Falls Sie BEA WebLogic in einer niedrigeren Version als 9.x verwenden, müssen Sie den WebLogic-Deployment-Descriptor in einem anderen Format erstellen (und mit DTD statt XSD).

  9. Client-Anwendung

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinclient' die folgende Datei 'HelloWorldApp.java':

    package meinclient;
    
    import javax.naming.*;
    import javax.rmi.PortableRemoteObject;
    import meinsessionbeanpkg.IHelloWorldHome;
    import meinsessionbeanpkg.IHelloWorld;
    
    public class HelloWorldApp
    {
      public static void main( String[] args ) throws Exception
      {
        Context ctx = new InitialContext();
        // Je nach Java EE Application Server bzw. Deployment-Descriptor:
        //   "HelloWorld" oder
        //   "meinsessionbeanpkg.IHelloWorldHome"
        Object  ref = ctx.lookup( "HelloWorld" );
        IHelloWorldHome home = (IHelloWorldHome) PortableRemoteObject.narrow(
                                                   ref, IHelloWorldHome.class );
        IHelloWorld helloWorld = home.create( (0<args.length)?args[0]:null );
        System.out.println( helloWorld.sayHello() );
        System.out.println( helloWorld.sayHello() );
        System.out.println( helloWorld.getCount() );
      }
    }
    
  10. Verzeichnisstruktur

    Die Verzeichnisstruktur muss jetzt in etwa folgendermaßen aussehen (überprüfen Sie es mit tree /F):

    [\MeinWorkspace]
      '- [MeinEjb2Projekt]
           |- [lib]
           |    '- ... [z.B. javax.ejb_3.0.1.jar, jbossall-client.jar]
           |- [src]
           |    |- [meinclient]
           |    |    '- HelloWorldApp.java
           |    '- [meinsessionbeanpkg]
           |         |- ejb-jar.xml
           |         |- HelloWorldImpl.java
           |         |- IHelloWorld.java
           |         |- IHelloWorldHome.java
           |         '- ... eventuell weblogic-ejb-jar.xml o.a.
           |- build.properties
           |- build.xml
           |- jndi.properties
           '- log4j.properties
    

    Wichtig: Im '<MeinEjb2Projekt>'-Verzeichnis müssen sich die Dateien 'build.properties', 'build.xml', 'jndi.properties' und 'log4j.properties' befinden.

  11. Aufruf

    Bei laufendem Java EE Application Server (JBoss, WebLogic, GlassFish, ...) öffnen Sie ein Kommandozeilenfenster und geben die folgenden Kommandos ein:

    cd \MeinWorkspace\MeinEjb2Projekt

    ant -Dclntapp=meinclient.HelloWorldApp -Dsrvjar=HelloWorldBean.jar -Dsrvpkg=meinsessionbeanpkg

    (Falls Ihr Webbrowser Zeilen umgebrochen hat: Das 'ant ...'-Kommando ist eine Kommandozeile.)

    Sie erhalten die Ausgabe:

    Hallo Welt
    Hallo Welt
    2

    Falls Sie nicht Ihren Java EE Application Server für Auto-Deployment konfiguriert haben, müssen Sie zuerst das genannte 'ant'-Kommando ausführen, um die 'HelloWorldBean.jar'-Datei zu erzeugen, dann diese 'HelloWorldBean.jar'-Datei über die Administrationskonsole des Java EE Application Servers deployen und anschließend das 'ant'-Kommando erneut ausführen, um den Client aufzurufen.

  12. Bemerkungen

    Wenn Sie etwas an den Sourcen ändern und erneut 'ant ...' aufrufen, wird neu deployt und der Java EE Application Server registriert automatisch die Änderung (bei Auto-Deployment). Der Java EE Application Server braucht nicht neu gestartet zu werden.

    Falls Sie eine Fehlermeldung ähnlich zu 'package javax.ejb does not exist', 'java.lang.NoClassDefFoundError' oder 'javax.naming.NoInitialContextException: Cannot instantiate class' erhalten, fehlt im 'lib'-Verzeichnis oder im Classpath eine benötigte .jar-Library (z.B. 'jbossall-client.jar', 'javax.ejb_3.0.1.jar', 'weblogic.jar', ...).

    Falls Sie eine Fehlermeldung ähnlich zu 'javax.naming.NoInitialContextException: Need to specify class name ...: java.naming.factory.initial' erhalten, befindet sich das Verzeichnis von 'jndi.properties' nicht im Classpath.

    Wenn Sie eine Fehlermeldung ähnlich zu 'NameNotFoundException: HelloWorld not bound' oder 'EJBException: Could not instantiate bean' erhalten, konnte der Java EE Application Server den Deploy-Vorgang nicht schnell genug abschließen. Wiederholen Sie den 'ant ...'-Aufruf einfach.

    Falls Sie eine Fehlermeldung ähnlich zu 'NameNotFoundException: HelloWorld not found' erhalten, erwartet Ihr Java EE Application Server einen anderen JNDI-Namen. Probieren Sie in 'HelloWorldApp.java' beim 'ctx.lookup()'-Aufruf zum Beispiel 'meinsessionbeanpkg.IHelloWorldHome' statt 'HelloWorld'. Besser: Sehen Sie mit dem JNDI-Auslese-Programm nach, welchen JNDI-Namen Ihr Java EE Application Server vergeben hat.

  13. Auch wenn kein Fehler auftritt: Sehen Sie sich den JNDI-Context mit dem JNDI-Auslese-Programm an:
    Außer dem durch das Programmbeispiel generierten JNDI-Eintrag (z.B. 'HelloWorld' oder 'meinsessionbeanpkg.IHelloWorldHome') finden Sie noch weitere vergebene JNDI-Namen.

    Falls JNDI nicht funktioniert: Fahren Sie den Java EE Application Server herunter und überprüfen Sie, ob der JNDI-Port (z.B. für JBoss: 1099) durch ein anderes Programm belegt ist. Rufen Sie hierfür in einem Kommandozeilenfenster zum Beispiel netstat -a, netstat -a -b -n oder telnet 127.0.0.1 1099 auf.
    Je nach Java EE Application Server kann der JNDI-Port eventuell notfalls verstellt werden (z.B. bei JBoss über die JBoss-NamingService-MBean entweder über die JMX Console oder die entsprechende jboss-xxx.xml-Datei).

  14. Eclipse

    Falls Sie das Projekt in Eclipse IDE for Java EE Developers ab Version 3.3 (inkl. WTP) bearbeiten wollen, richten Sie in Eclipse Ihren Java EE Application Server ein (unter 'File' | 'New' | 'Other...' | '[+] Server' | 'Server' | ...).

    Falls Sie das Projekt in einer älteren Eclipse-Version bearbeiten wollen, gehen Sie wie folgt vor:

    Falls Sie es nicht bereits dort angelegt haben, kopieren Sie das Projektverzeichnis 'MeinEjb2Projekt' in Ihr Eclipse-Workspace-Verzeichnis (z.B. 'D:\MeinWorkspace').
    Wählen Sie 'File' | 'New' | 'Project...' | 'Java Project' | 'Project Name = MeinEjb2Projekt' | 'Next'.
    Falls zugeklappt: Klappen Sie mit Klick auf das kleine schwarze Dreieck die 'Details' auf.
    Wählen Sie unter 'Details' die Option 'Add project 'MeinEjb2Projekt' to build path'.
    Achten Sie darauf, dass als 'Default output folder' eingestellt ist: 'MeinEjb2Projekt/bin'.
    Klicken Sie auf den Tabulatorreiter 'Libraries', wählen Sie 'Add External Jars...' und das 'lib'-Verzeichnis, markieren Sie darin alle Dateien und wählen Sie 'Öffnen' | 'Finish'.
    Klicken Sie im 'Package Explorer' auf das [+] vor 'MeinEjb2Projekt' und mit der rechten Maustaste auf 'build.xml' | 'Run As' | 'Ant Build...'.
    Achten Sie darauf, dass unter dem Tabulatorreiter 'Targets' in der Target-Liste 'run-client' angewählt ist.
    Schalten Sie um auf den Tabulatorreiter 'Properties', schalten Sie die Option 'Use global properties as specified in the Ant runtime preferences' aus und fügen Sie mit 'Add Property...' drei Properties hinzu: 'clntapp=meinclient.HelloWorldApp', 'srvjar=HelloWorldBean.jar' und 'srvpkg=meinsessionbeanpkg'.
    Klicken Sie auf 'Run'.

    Falls Sie nicht deployen wollen, sondern nur den Client ändern und aufrufen wollen, genügt auch, wenn Sie die Java-Sourcedatei 'HelloWorldApp' (mit der 'main()'-Methode) öffnen, den Fokus und Textcursor in diese Datei setzen und wählen: 'Run' | 'Run as' | 'Java Application'.



Beispiel: CMP Entity Bean

  1. Normalerweise werden Entity Beans von Session Beans aufgerufen und nicht von externen Clients. Um es einfacher zu halten, wird im folgenden Demobeispiel trotzdem die Entity Bean direkt von einem externen Client aufgerufen. Bitte beachten Sie, dass dies in einigen neueren Java EE Application Servern (z.B. WebLogic 10.3.4) nicht mehr funktioniert.

    Dieses Beispiel setzt voraus, dass die oben aufgeführten "Gemeinsamen Dateien für die Beispiele" vorbereitet sind, sowie dass JBoss 4.x oder BEA WebLogic 9.2 installiert ist. Für andere Java EE Application Server müsste es angepasst werden.

  2. EJB Home Interface

    Entity Beans müssen eindeutige Primary-Key-Werte haben, über die sie gefunden werden können (analog zu Primary Keys in relationalen Datenbanken), auch wenn als Speichermedium keine relationale Datenbank eingesetzt wird. Wird der Primary-Key-Wert als Objekt realisiert, muss darauf geachtet werden, dass das Objekt serialisierbar sein muss und funktionierende 'hashCode()'- und 'equals()'-Methoden haben muss.

    Anders als in Session Beans müssen bei Entity Beans im Home Interface zusätzlich zur 'create()'-Methode zum Erzeugen der Bean weitere Methoden zum Finden der Bean deklariert werden, zumindest 'findByPrimaryKey()'. Alle Finder-Methoden müssen mit 'find...' beginnen.

    Legen Sie in Ihrem Projektverzeichnis '<MeinEjb2Projekt>' das Unterverzeichnis 'src\meinentitybeanpkg' an und speichern Sie darin die folgende Datei 'IKundeHome.java':

    package meinentitybeanpkg;
    
    import java.util.Collection;
    import java.rmi.RemoteException;
    import javax.ejb.*;
    
    public interface IKundeHome extends EJBHome
    {
      public IKunde     create()
                        throws RemoteException, CreateException;
      public IKunde     findByPrimaryKey( Integer kundennummer )
                        throws RemoteException, FinderException;
      public Collection findByName( String name )
                        throws RemoteException, FinderException;
    }
    
  3. EJB Remote Interface

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'IKunde.java':

    package meinentitybeanpkg;
    
    import java.util.Date;
    import java.rmi.RemoteException;
    import javax.ejb.EJBObject;
    
    public interface IKunde extends EJBObject
    {
      Integer getKundennummer()      throws RemoteException;
      String  getName()              throws RemoteException;
      void    setName( String name ) throws RemoteException;
      Date    getDatum()             throws RemoteException;
      void    setDatum( Date datum ) throws RemoteException;
    }
    
  4. Implementierung der CMP 2.0 Entity Bean

    Seit CMP 2.0 muss die Implementierung einer CMP Entity Bean eine abstrakte Klasse sein, in der für die persistenten Felder keine Elementvariablen und nur abstrakte Getter- und Setter-Methoden definiert werden.

    In der Bean-Implementierung müssen alle im Remote Interface deklarierten Methoden implementiert werden und es muss zu jeder im Home Interface definierten 'create()'-Methode das passende Gegenstück mit den selben Parametern als 'ejbCreate()'-Methode geben.
    Bitte beachten Sie, dass die im Home Interface deklarierten 'create()'- und 'find...()'-Methoden das Remote Interface oder eine Collection davon returnieren, während in der Bean-Implementierung bei den 'ejbCreate()'-Methoden als Rückgabewert ein Primary-Key-Objekt oder eine Collection davon deklariert wird.
    Eine weitere Besonderheit ist, dass in CMP-Beans die 'ejbCreate()'-Methoden 'null' returnieren (die Deklaration des Rückgabewerts besteht aus Kompatibilitätsgründen zu früheren EJB-Versionen).

    Die im Home Interface deklarierte 'findByPrimaryKey()'-Methode darf in der CMP-Bean nicht implementiert werden, sie wird vom Container zur Verfügung gestellt. Andere 'find...()'-Methoden werden normalerweise nicht ausprogrammiert, sondern in der 'ejb-jar.xml' als 'EJBQL'-Abfrage formuliert (EJB Query Language) (siehe unten 'ejb-jar.xml').

    Die hier vorgestellte Bean-Implementierung erzeugt neue Primary Keys über die zusätzliche Datenbanktabelle 'kunde_id_seq' über die Methode 'getNextId()'. Je nach Datenbank gibt es hierfür auch wesentlich elegantere Wege. Bei EJB Version 3.0 kann die automatische Generierung durch folgende Annotation erreicht werden:
    '@Id(generate=GeneratorType.AUTO)'.

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'KundeImpl.java':

    package meinentitybeanpkg;
    
    import java.util.Date;
    import java.sql.*;
    import javax.sql.*;
    import javax.ejb.*;
    import javax.naming.*;
    
    public abstract class KundeImpl implements EntityBean
    {
      private EntityContext ctx;
    
      public abstract Integer getKundennummer();
      public abstract void    setKundennummer( Integer kundennummer );
    
      public abstract String  getName();
      public abstract void    setName( String name );
    
      public abstract Date    getDatum();
      public abstract void    setDatum( Date datum );
    
      public Integer ejbCreate() throws CreateException
      {
        try {
          setKundennummer( new Integer( getNextId() ) );
          return null;
        } catch( SQLException ex ) {
          throw new CreateException( ex.toString() );
        }
      }
    
      public void ejbPostCreate() {}
      public void ejbActivate() {}
      public void ejbPassivate() {}
      public void ejbLoad() {}
      public void ejbStore() {}
      public void ejbRemove() {}
    
      public void setEntityContext( EntityContext entityContext )
      {
        ctx = entityContext;
      }
    
      public void unsetEntityContext()
      {
        ctx = null;
      }
    
      protected int getNextId() throws SQLException
      {
        final String sErr = "Fehler: Kein neuer Primary Key erzeugbar. ";
        Connection conn = null;
        try {
          InitialContext context = new InitialContext();
          // Je nach Java EE Application Server und DataSource-Definition:
          //   JBoss:         "java:/MeinDatasourceJndiName"
          //   WebLogic:      "jdbc/MeinDatasourceJndiName"
          //   andere event.: "java:comp/env/jdbc/MeinDatasourceJndiName"
          DataSource ds = (DataSource) context.lookup( "java:/MeinDatasourceJndiName" );
          conn = ds.getConnection();
          PreparedStatement ps;
          try {
            ps = conn.prepareStatement(
                "UPDATE kunde_id_seq SET next_kunde_id = next_kunde_id + 1" );
            if( 1 != ps.executeUpdate() )
              throw new SQLException();
          } catch( SQLException ex ) {
            Statement st = conn.createStatement();
            st.execute( "CREATE TABLE kunde_id_seq( next_kunde_id INTEGER NOT NULL );" );
            st.execute( "INSERT INTO kunde_id_seq VALUES ( 0 );" );
            st.close();
            ps = conn.prepareStatement(
                "UPDATE kunde_id_seq SET next_kunde_id = next_kunde_id + 1" );
            if( 1 != ps.executeUpdate() )
              throw new SQLException( sErr );
          }
          ps.close();
          ps = conn.prepareStatement( "SELECT next_kunde_id FROM kunde_id_seq" );
          ResultSet rs = ps.executeQuery();
          if( rs.next() )
            return rs.getInt( "next_kunde_id" );
          throw new SQLException( sErr );
        } catch( NamingException ex ) {
          throw new SQLException( sErr + ex.toString() );
        }
        finally {
          try { if( null != conn ) conn.close(); } catch( Exception ex ) {/*ok*/}
        }
      }
    }
    

    Passen Sie die Zeile 'DataSource ds = (DataSource) context.lookup( "java:/MeinDatasourceJndiName" );' an Ihren Java EE Application Server und Ihre DataSource-Definition an.

    Bitte beachten Sie, dass die hier gezeigte Variante der Primary-Key-Generierung nur der Einfachheit halber gewählt wurde und nicht für eine Mehrbenutzer-Umgebung geeignet ist (verwenden Sie stattdessen eine Datenbank-eigene Sequenz).

  5. Standard EJB Deployment-Descriptor

    Vom eingesetzten Java EE Application Server unabhängige Einstellungen erfolgen im Standard EJB Deployment-Descriptor.

    Sollen zusätzlich zur obligatorischen 'findByPrimaryKey()'-Methode weitere 'find...()'-Methoden implementiert werden, werden im EJB Deployment-Descriptor die dazu notwendigen SQL-Abfrage-Strings definiert, und zwar in 'EJBQL' (EJB Query Language). Im folgenden Beispiel ist es die Methode 'findByName()' mit dem EJBQL-String 'SELECT OBJECT(k) FROM Kunde AS k WHERE k.name like ?1', der als CDATA (nicht zu parsende 'Character-Daten') deklariert ist.

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'ejb-jar.xml':

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE ejb-jar PUBLIC
      "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
      "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
    <ejb-jar>
      <enterprise-beans>
        <entity>
    
          <display-name> Kunde </display-name>
          <ejb-name> Kunde </ejb-name>
          <home> meinentitybeanpkg.IKundeHome </home>
          <remote> meinentitybeanpkg.IKunde </remote>
          <ejb-class> meinentitybeanpkg.KundeImpl </ejb-class>
    
          <persistence-type> Container </persistence-type>
          <prim-key-class> java.lang.Integer </prim-key-class>
          <reentrant> false </reentrant>
          <cmp-version> 2.x </cmp-version>
          <abstract-schema-name> Kunde </abstract-schema-name>
    
          <cmp-field><field-name> kundennummer </field-name></cmp-field>
          <cmp-field><field-name> name </field-name></cmp-field>
          <cmp-field><field-name> datum </field-name></cmp-field>
          <primkey-field> kundennummer </primkey-field>
    
          <resource-ref>
            <res-ref-name> jdbc/MeinDatasourceJndiName </res-ref-name>
            <res-type> javax.sql.DataSource </res-type>
            <res-auth> Container </res-auth>
          </resource-ref>
    
          <query>
            <query-method>
              <method-name> findByName </method-name>
              <method-params>
                <method-param> java.lang.String </method-param>
              </method-params>
            </query-method>
            <ejb-ql>
              <![CDATA[SELECT OBJECT(k) FROM Kunde AS k WHERE k.name like ?1]]>
            </ejb-ql>
          </query>
    
        </entity>
      </enterprise-beans>
    
      <assembly-descriptor>
        <container-transaction>
          <method>
            <ejb-name> Kunde </ejb-name>
            <method-name> * </method-name>
          </method>
          <trans-attribute> Required </trans-attribute>
        </container-transaction>
      </assembly-descriptor>
    
    </ejb-jar>
    
  6. Spezifische Deployment-Descriptoren

    Vom Java EE Application Server abhängige Einstellungen erfolgen in Extra-Deployment-Descriptoren. Einige Java EE Application Server können auch ohne weitere Deployment-Descriptoren auskommen und Standardeinstellungen verwenden. Zum Beispiel würde JBoss seine integrierte hSqlDb-Datenbank einsetzen. In diesem Beispiel soll jedoch die oben definierte DataSource verwendet werden.

  7. JBoss CMP-JDBC-Deployment-Descriptor

    Falls Sie JBoss verwenden:
    Legen Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'jbosscmp-jdbc.xml' an:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE jbosscmp-jdbc PUBLIC "-//JBoss//DTD JBOSSCMP-JDBC 3.0//EN"
              "http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_3_0.dtd">
    <jbosscmp-jdbc>
      <enterprise-beans>
        <entity>
          <ejb-name> Kunde </ejb-name>
          <datasource> java:/MeinDatasourceJndiName </datasource>
          <datasource-mapping> mySQL </datasource-mapping>
          <table-name> Kunde </table-name>
        </entity>
      </enterprise-beans>
    </jbosscmp-jdbc>
    
  8. Oracle WebLogic EJB- und CMP-RDBMS-Deployment-Descriptor

    Falls Sie Oracle WebLogic ab Version 9.x einsetzen:

    Legen Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'weblogic-ejb-jar.xml' an:

    <?xml version="1.0" encoding="UTF-8"?>
    <weblogic-ejb-jar xmlns="http://www.bea.com/ns/weblogic/90"
                      xmlns:j2ee="http://java.sun.com/xml/ns/j2ee"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xsi:schemaLocation="http://www.bea.com/ns/weblogic/90
                                          http://www.bea.com/ns/weblogic/90/weblogic-ejb-jar.xsd">
      <weblogic-enterprise-bean>
        <ejb-name>Kunde</ejb-name>
        <entity-descriptor>
          <entity-cache>
            <max-beans-in-cache>1000</max-beans-in-cache>
            <read-timeout-seconds>600</read-timeout-seconds>
            <concurrency-strategy>Database</concurrency-strategy>
          </entity-cache>
          <persistence>
            <persistence-use>
              <type-identifier>WebLogic_CMP_RDBMS</type-identifier>
              <type-version>6.0</type-version>
              <type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-storage>
            </persistence-use>
          </persistence>
        </entity-descriptor>
        <transaction-descriptor>
          <trans-timeout-seconds>30</trans-timeout-seconds>
        </transaction-descriptor>
        <enable-call-by-reference>true</enable-call-by-reference>
        <jndi-name>Kunde</jndi-name>
      </weblogic-enterprise-bean>
    </weblogic-ejb-jar>
    

    Legen Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'weblogic-cmp-rdbms-jar.xml' an:

    <?xml version="1.0" encoding="UTF-8"?>
    <weblogic-rdbms-jar xmlns="http://www.bea.com/ns/weblogic/90"
                        xmlns:j2ee="http://java.sun.com/xml/ns/j2ee"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xsi:schemaLocation="http://www.bea.com/ns/weblogic/90
                                            http://www.bea.com/ns/weblogic/90/weblogic-rdbms20-persistence.xsd">
      <weblogic-rdbms-bean>
        <ejb-name>Kunde</ejb-name>
        <data-source-jndi-name>jdbc/MeinDatasourceJndiName</data-source-jndi-name>
        <table-map>
          <table-name>Kunde</table-name>
          <field-map>
            <cmp-field>kundennummer</cmp-field>
            <dbms-column>kundennummer</dbms-column>
          </field-map>
          <field-map>
            <cmp-field>name</cmp-field>
            <dbms-column>name</dbms-column>
          </field-map>
          <field-map>
            <cmp-field>datum</cmp-field>
            <dbms-column>datum</dbms-column>
          </field-map>
        </table-map>
      </weblogic-rdbms-bean>
    </weblogic-rdbms-jar>
    

    Falls Sie BEA WebLogic in einer niedrigeren Version als 9.x verwenden, müssen Sie die beiden WebLogic-Deployment-Descriptoren in einem anderen Format erstellen (und mit DTD statt XSD).

  9. Client-Anwendung

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinclient' die folgende Datei 'EntityApp.java':

    package meinclient;
    
    import java.util.*;
    import java.text.SimpleDateFormat;
    import javax.naming.*;
    import javax.rmi.PortableRemoteObject;
    import meinentitybeanpkg.IKundeHome;
    import meinentitybeanpkg.IKunde;
    
    public class EntityApp
    {
      public static void main( String[] args ) throws Exception
      {
        SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd" );
        Context ctx = new InitialContext();
        // Je nach Java EE Application Server bzw. Deployment-Descriptor:
        //   "Kunde" oder
        //   "meinentitybeanpkg.IKundeHome"
        Object  ref = ctx.lookup( "Kunde" );
        IKundeHome home = (IKundeHome) PortableRemoteObject.narrow( ref, IKundeHome.class );
    
        // Kunde neu anlegen:
        IKunde kunde = home.create();
        kunde.setName( "Hans Otto" );
        kunde.setDatum( new Date() );
        System.out.println( "Kunde neu angelegt: Nr. " + kunde.getKundennummer()
                            + ", "                     + kunde.getName()
                            + ", Datum "    + df.format( kunde.getDatum() ) );
    
        // Bestimmte Kunden auslesen:
        System.out.println( "\nKunden mit 'Ott' im Namen:" );
        Collection coll = home.findByName( "%Ott%" );
        Iterator it = coll.iterator();
        while( it.hasNext() )
        {
          kunde = (IKunde) it.next();
          System.out.println( "\nKundennummer : " +            kunde.getKundennummer() );
          System.out.println(   "Name         : " +            kunde.getName() );
          System.out.println(   "Datum        : " + df.format( kunde.getDatum() ) );
        }
      }
    }
    
  10. Verzeichnisstruktur

    Die Verzeichnisstruktur muss jetzt in etwa folgendermaßen aussehen (überprüfen Sie es mit tree /F):

    [\MeinWorkspace]
      '- [MeinEjb2Projekt]
           |- [bin]
           |    '- ...
           |- [lib]
           |    '- ... [z.B. javax.ejb_3.0.1.jar, jbossall-client.jar]
           |- [src]
           |    |- [meinclient]
           |    |    |- EntityApp.java
           |    |    '- HelloWorldApp.java
           |    |- [meinentitybeanpkg]
           |    |    |- ejb-jar.xml
           |    |    |- IKunde.java
           |    |    |- IKundeHome.java
           |    |    |- KundeImpl.java
           |    |    '- jbosscmp-jdbc.xml  [bzw. weblogic-cmp-rdbms-jar.xml und weblogic-ejb-jar.xml]
           |    '- [meinsessionbeanpkg]
           |         |- ejb-jar.xml
           |         |- HelloWorldImpl.java
           |         |- IHelloWorld.java
           |         |- IHelloWorldHome.java
           |         '- ... eventuell weblogic-ejb-jar.xml o.a.
           |- build.properties
           |- build.xml
           |- jndi.properties
           '- log4j.properties
    

    Wichtig: Im '<MeinEjb2Projekt>'-Verzeichnis müssen sich die Dateien 'build.properties', 'build.xml', 'jndi.properties' und 'log4j.properties' befinden.

  11. Aufruf

    Bei laufendem Java EE Application Server (JBoss, WebLogic, ...) öffnen Sie ein Kommandozeilenfenster und geben die folgenden Kommandos ein:

    cd \MeinWorkspace\MeinEjb2Projekt

    ant -Dclntapp=meinclient.EntityApp -Dsrvjar=KundeEntityBean.jar -Dsrvpkg=meinentitybeanpkg

    ant -Dclntapp=meinclient.EntityApp -Dsrvjar=KundeEntityBean.jar -Dsrvpkg=meinentitybeanpkg

    Sie erhalten die Ausgabe:

    Kunde neu angelegt: Nr. 2, Hans Otto, Datum 2005-02-28
    Kunden mit 'Ott' im Namen:
    Kundennummer : 1
      Name       : Hans Otto
      Datum      : 2005-02-28
    Kundennummer : 2
      Name       : Hans Otto
      Datum      : 2005-02-28

    Wenn Sie eine Fehlermeldung ähnlich zu 'NameNotFoundException: Kunde not bound' oder 'EJBException: Could not instantiate bean' erhalten, konnte der Java EE Application Server den Deploy-Vorgang nicht schnell genug abschließen. Wiederholen Sie den 'ant ...'-Aufruf einfach.

  12. Eclipse

    Falls Sie das Projekt in Eclipse bearbeiten wollen, gehen Sie vor wie oben beschrieben.
    Falls Sie außerhalb von Eclipse Dateien verändert oder hinzugefügt haben, müssen Sie in Eclipse die entsprechenden Projekte im 'Package Explorer' markieren und mit 'F5' einen 'Refresh' durchführen.
    Außerdem müssen Sie für dieses Beispiel die build.xml-Properties anders einstellen:
    'clntapp=meinclient.EntityApp', 'srvjar=KundeEntityBean.jar' und 'srvpkg=meinentitybeanpkg'.

  13. Sehen Sie sich den JNDI-Context mit dem JNDI-Auslese-Programm an:
    Dort finden Sie den durch das Programmbeispiel generierten JNDI-Eintrag 'Kunde'.

  14. Sie können sich das Ergebnis in der MySQL-Datenbank zum Beispiel mit 'SQuirreL' ansehen.



Beispiel: MDB (Message Driven Bean)

Informationen zum JMS (Java Message Service) finden Sie in jee-jms.htm.

Dieses Beispiel setzt die oben aufgeführten "Gemeinsamen Dateien für die Beispiele" und JBoss voraus. Für andere Java EE Application Server müsste es angepasst werden.

  1. Implementierung der MDB (Message Driven Bean)

    Da Message Driven Beans nicht direkt von Clients aufgerufen werden, benötigen Sie keine 'Home' und 'Remote Interfaces'.

    Im Folgenden wird eine MDB vorgestellt, die Nachrichten eines JMS-'Topics' abruft.

    Legen Sie in Ihrem Projektverzeichnis '<MeinEjb2Projekt>' das Unterverzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' an und speichern Sie darin die folgende Datei 'WetterMDB.java':

    package meinmdbpkg;
    
    import javax.ejb.*;
    import javax.jms.*;
    
    public class WetterMDB implements MessageDrivenBean, MessageListener
    {
      private MessageDrivenContext ejbContext;
    
      public void onMessage( Message message )
      {
        try {
          MapMessage mapMessage = (MapMessage) message;
          System.out.println( "WetterMDB: Wl=" + mapMessage.getString( "Wetterlage" )
                                      + " Wr=" + mapMessage.getString( "Windrichtung" )
                                      + " T="  + mapMessage.getDouble( "Temperatur" ) + "C" );
          message.acknowledge();
        } catch( Exception ex ) {
          System.out.println( ex.getMessage() );
        }
      }
    
      public void ejbCreate() {}
    
      public void ejbRemove()
      {
        ejbContext = null;
      }
    
      public void setMessageDrivenContext( MessageDrivenContext messageDrivenContext )
      {
        this.ejbContext = messageDrivenContext;
      }
    }
    
  2. JMS-Topic-Service

    JMS-Queues und -Topics müssen beim JMS-Provider angemeldet werden, indem entsprechende '*-service.xml'-Dateien in das JBoss-Deploy-Verzeichnis kopiert werden (hierfür ist in der 'build.xml' beim 'deploy'-Target der '<copy ... "*-service.xml" ...'-Ausdruck).

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' die folgende Datei 'wetter-service.xml':

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Destination without a configured SecurityManager or without a SecurityConf
         will default to role guest with read=true, write=true, create=false. -->
    <server>
      <mbean code="org.jboss.mq.server.jmx.Topic"
             name="jboss.mq.destination:service=Topic,name=WetterTopic">
        <depends optional-attribute-name="DestinationManager">
          jboss.mq:service=DestinationManager
        </depends>
        <depends optional-attribute-name="SecurityManager">
          jboss.mq:service=SecurityManager
        </depends>
      </mbean>
    </server>
    
  3. Standard EJB Deployment-Descriptor

    Von der vorhandenen JMS-Implementierung unabhängige Einstellungen (z.B. ob JMS Queue oder JMS Topic) erfolgen im Standard EJB Deployment-Descriptor.

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' die folgende Datei 'ejb-jar.xml':

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE ejb-jar
      PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
      "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
    <ejb-jar>
      <enterprise-beans>
        <message-driven>
          <ejb-name> WetterMDB </ejb-name>
          <ejb-class> meinmdbpkg.WetterMDB </ejb-class>
          <transaction-type> Container </transaction-type>
          <acknowledge-mode> Auto-acknowledge </acknowledge-mode>
          <message-driven-destination>
            <destination-type> javax.jms.Topic </destination-type>
            <subscription-durability> NonDurable </subscription-durability>
          </message-driven-destination>
        </message-driven>
      </enterprise-beans>
    </ejb-jar>
    
  4. JBoss MDB Deployment-Descriptor

    Vom JMS-Provider abhängige Einstellungen (z.B. die Verknüpfung mit einem bestimmten Topic per JNDI-Namen) erfolgen in einem Extra-Deployment-Descriptor.

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' die folgende Datei 'jboss.xml':

    <?xml version="1.0"?>
    <jboss>
      <enterprise-beans>
        <message-driven>
          <ejb-name> WetterMDB </ejb-name>
          <configuration-name> Standard Message Driven Bean </configuration-name>
          <destination-jndi-name> topic/WetterTopic </destination-jndi-name>
        </message-driven>
      </enterprise-beans>
    </jboss>
    
  5. JMS-Nachrichtensender

    Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinclient' die folgende Datei 'MdbApp.java':

    package meinclient;
    
    import javax.jms.*;
    import javax.naming.*;
    
    public class MdbApp
    {
      public static void main( String[] args ) throws NamingException, JMSException
      {
        final String[]  WETTERLAGEN    = { "Sonnig", "Wolkig", "Regen " };
        final String[]  WINDRICHTUNGEN = { "Nord", "West", "Sued", "Ost " };
        final String    WETTER_TOPIC   = "topic/WetterTopic";
        Context         ctx     = null;
        TopicConnection connect = null;
        TopicSession    session = null;
        Topic           topic   = null;
        TopicPublisher  sender  = null;
        try {
          ctx = new InitialContext();
          TopicConnectionFactory fact = (TopicConnectionFactory)
                                        ctx.lookup( "ConnectionFactory" );
          connect = fact.createTopicConnection();
          session = connect.createTopicSession( false, Session.AUTO_ACKNOWLEDGE );
          try {
            topic = (Topic) ctx.lookup( WETTER_TOPIC );
          } catch( NameNotFoundException ex ) {
            topic = session.createTopic( WETTER_TOPIC );
            ctx.bind( WETTER_TOPIC, topic );
          }
          sender = session.createPublisher( topic );
          connect.start();
          for( int i=0; i<10; i++ ) {
            int rndm = (int) (Math.random() * 300);
            String wetterlage   = WETTERLAGEN[rndm % WETTERLAGEN.length];
            String windrichtung = WINDRICHTUNGEN[rndm % WINDRICHTUNGEN.length];
            double temperatur   = (double) (rndm - 50) / 10;
            MapMessage msg = session.createMapMessage();
            msg.setString( "Wetterlage",   wetterlage );
            msg.setString( "Windrichtung", windrichtung );
            msg.setDouble( "Temperatur",   temperatur );
            sender.publish( msg );
            System.out.println( "Sending Wl=" + wetterlage
                                     + " Wr=" + windrichtung
                                     + " T="  + temperatur + "C" );
            System.out.println( "Sending " + msg.toString() );
          }
        } finally {
          try { if( null != sender  ) sender.close();  } catch( Exception ex ) {/*ok*/}
          try { if( null != session ) session.close(); } catch( Exception ex ) {/*ok*/}
          try { if( null != connect ) connect.close(); } catch( Exception ex ) {/*ok*/}
          try { if( null != ctx     ) ctx.close();     } catch( Exception ex ) {/*ok*/}
        }
      }
    }
    
  6. Im Verzeichnis '<MeinEjb2Projekt>' müssen sich die Dateien 'build.properties', 'build.xml', 'jndi.properties' und 'log4j.properties' befinden.

    Bei laufendem JBoss öffnen Sie ein Kommandozeilenfenster und geben die folgenden Kommandos ein:

    cd \MeinWorkspace\MeinEjb2Projekt

    ant -Dclntapp=meinclient.MdbApp -Dsrvjar=WetterMDB.jar -Dsrvpkg=meinmdbpkg

    In diesem Kommandozeilenfenster erhalten Sie die Ausgabe der gesendeten Wetternachrichten. In dem Kommandozeilenfenster von JBoss sehen Sie die empfangenen Wetternachrichten.

    Mit etwas Glück können Sie vielleicht beobachten, dass die empfangenen Nachrichten nicht immer in der Reihenfolge der gesendeten Nachrichten erscheinen.

  7. Sehen Sie sich den JNDI-Context mit dem JNDI-Auslese-Programm an:
    Unter 'topic' finden Sie den durch das MDB-Programmbeispiel generierten JNDI-Eintrag 'topic/WetterTopic'.



Links auf weiterführende Informationen





Weitere Themen: andere TechDocs | JSP | JMS | SQL | Hibernate
© 1998-2007 Torsten Horn, Aachen