Beispiel: Einfache Container Managed Entity Bean (JBoss 4.0)
Beispiel für eine Container Managed Entity Bean, auf die per Applicationclient zugegriffen wird.
Die Deployanleitung bezieht sich auf JBoss 4.0.3.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die
Importanleitung findet man im Stateless-Beispiel): KuchenSingle.ear
ACHTUNG: Nach dem Import XDoclet-Builder im EJB-Projekt aktivieren !
Aufbau des Beispieles
a) Entity Bean-Klasse mit Remote-Interfaces.
b) Application Client
Anlegen der Application
Ein "EAR Application Project" mit dem Namen "KuchenSingle" erstellen.
Zu erzeugende Module definieren. Dieses Beispiel benötigt ein EJB-Projekt und
ein Anwendungsclientprojekt.
Die J2EE-Hierarchie sollte so aussehen:
Anlegen der Entity Bean
Es wird eine neue Entity Bean angelegt. Dazu in der J2EE-Hierarchie "EJB Projects" -> "KuchenSingleEJB" ->
"KuchenSingleEJB" -> "Entity Beans" wählen und im Contextmenü den Punkt "New..." -> "Other".
Eine neue Enterprise Java Bean zufügen.
Im ersten Schritt wird der Typ der Bean gewählt. ACHTUNG: Da es keine separate Option "Entity Bean" gibt
fügen wir eine Session Bean zu !
Das Package ist "com.knauf.ejb.kuchen", der Klassenname "KuchenSingleBean".
Im nächsten Schritt belassen wir die Defaults, auch beim Bean-Typ ("State Type" ist natürlich für eine Entity-Bean falsch !).
Im folgenden Schritt entfernen wir das Interface "javax.ejb.SessionBean" und ersetzen es durch
"javax.ejb.EntityBean".
Nach dem Erzeugen ist die Datei "ejb-jar.xml" im EJB-Projekt leider fehlerhaft (das Element "<entity>" hatte
kein öffnendes Tag ! Ein manuelles Einfügen hilft nicht da von XDoclet generiert. Aber nach dem
nächsten Schritt ist das behoben.
Zuerst einmal löschen wir die gesammelten generierten Dateien in "EJB Projects" -> "KuchenSingleEJB"
-> "KuchenSingleEJB" -> "ejbModule" -> "com.knauf.ejb.kuchen", so dass nur "KuchenSingleBean" übrig bleibt
(alle anderen Dateien werden von XDoclet generiert). Siehe Screenshot.
Wir ändern die XDoclet-Deklaration von "KuchenSingleBean":
Vom Plugin wurde dieses angelegt:
* @ejb.bean name="KuchenSingle"
* description="A session bean named KuchenSingle"
* display-name="KuchenSingle"
* jndi-name="KuchenSingle"
* type="Stateless"
* transaction-type="Container"
Wir ändern es in dieses ab:
* @ejb.bean name="KuchenSingle"
* description="Ein einzelner Kuchen"
* display-name="KuchenSingle"
* jndi-name="KuchenSingle"
* type="CMP"
* transaction-type="Container"
* cmp-version="2.x"
* view-type="remote"
* @ejb.pk
* class="java.lang.Object"
* generate="false"
* @jboss.unknown-pk
* class="java.lang.Integer"
* column-name="kuchenid"
* field-name="kuchenid"
* sql-type="INTEGER"
* jdbc-type="INTEGER"
* auto-increment="true"
* @jboss.entity-command
* name="hsqldb-fetch-key"
* @ejb.finder
* signature="java.util.Collection findAll()"
* query="select Object(o) from KuchenSingle o order by o.name"
Die einzelnen Angaben bedeuteten dieses:
- @ejb.bean: die Bean hat den Typ "Container managed Entity Bean" (="CMP"), die unterstützte
Version für Container Managed Entity Beans ist "2.x". Wir wollen hier nur Remote aufdie Bean zugreifen.
- @ejb.pk: Wir verwenden einen vom Container erzeugten Primary Key. Regeln hierfür aus dem J2EE-Standard:
- Es darf in der Bean-Klasse keine get/set-Methoden für das Feld geben.
- Rückgabe von "findByPrimaryKey" muss vom Typ "java.lang.Object" sein.
- Rückgabe von "ejbCreate" muss vom Typ "java.lang.Object" sein und den Wert NULL haben.
Wichtig ist hier das Attribut "generated=false" die XDoclet daran hindert eine Klasse "java.lang.Object" in
unserem Projekt anzulegen !
- @jboss.unknown-pk: JBoss-spezifische Angaben: Festlegen des Datentyps des für die Bean unbekannten
Primary Keys (Java-Klasse ist "java.lang.Integer", Spaltename in Datenbank ist "kuchenid", SQL-Typ und JDBC-Typ sind "INTEGER",
in der von JBoss erzeugten Bean-Implementation wird ein Feld namens "kuchenid" verwendet. Die Werte des Keys
werden automatisch hochgezählt.
- @jboss.entity-command: JBoss-spezifische Angaben für die Hypersonic-Datenbank: für das Erzeugen des PK-Werts
soll die Hypersonic-spezifische Funktion "CALL IDENTITY()" verwendet werden.
Weitere Infos zu den JBoss-spezifischen Verfahren für das Erzeugen des Primary Keys: http://docs.jboss.org/jbossas/jboss4guide/,
Kapitel 11.11.1
- @ejb.finder: Es soll eine find-Methode "findAll" erzeugt werden, die eine Collection zurückliefert (voller Name
der Parameter- und Rückgabewert-Klassen muss angegeben werden). Die dazugehörige EJB-QL - Query liest alle Kuchen
sortiert nach Namen ein.
Eine static Logger-Variable wird erzeugt.
Anschließend implementieren wir die Methode "ejbCreate" sowie die zugehörige "ejbPostCreate":
/**@ejb.create-method view-type="remote"
* @generated
*/
public Object ejbCreate () throws CreateException
{
KuchenSingleBean.logger.info("ejbCreate");
return null;
}
/**Nach dem Erzeugen eines Kuchen-Objekts, Name ist noch nicht gegeben.
* @exception CreateException Fehler beim Erzeugen.
*/
public void ejbPostCreate () throws CreateException
{
KuchenSingleBean.logger.info("ejbPostCreate");
}
Jetzt noch ein weiteres persistentes Feld "Name" einbauen und wir haben eine lauffähige Entity Bean erzeugt.
/**Der Name des Kuchens.
* @return
* @ejb.persistence column-name="name" sql-type="VARCHAR"
* @ejb.interface-method view-type="remote"
*/
public abstract String getName();
/**Der Name des Kuchens.
* @param str_Name Der neue Name
* @ejb.persistence column-name="name" sql-type="VARCHAR"
* @ejb.interface-method view-type="remote"
*/
public abstract void setName (String str_Name);
Wir geben hier den Spaltennamen ("kuchen") und den SQL-Typ an, nötig wäre beides nicht gewesen, das Tag
"@ejb.persistence" alleine hätte ausgereicht da Daten des Feld ein einfacher Java-Typ ist.
Die J2EE-Hierarchie sieht jetzt so aus:
"ejb-jar-xml" enthält die allgemeinen Bean-Definitionen:
<entity>
...
<ejb-class>com.knauf.ejb.kuchen.KuchenSingleCMP</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.Object</prim-key-class>
<reentrant>false</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>KuchenSingle</abstract-schema-name>
<cmp-field >
<description><![CDATA[Der Name des Kuchens.]]></description>
<field-name>name</field-name>
</cmp-field>
<query>
<query-method>
<method-name>findAll</method-name>
<method-params> </method-params>
</query-method>
<ejb-ql><![CDATA[select Object(o) from KuchenSingle o order by o.name]]></ejb-ql>
</query>
</entity>
Hier taucht nur die Primary Key-Klasse als "java.lang.Object" auf, in den Feldern ("cmp-field") wird sie nicht
erwähnt.
"jbosscmp-jdbc.xml" enthält JBoss-spezifische Angaben zur Bean.
<entity>
<ejb-name>KuchenSingle</ejb-name>
<cmp-field>
<field-name>name</field-name>
<column-name>name</column-name>
</cmp-field>
<unknown-pk>
<unknown-pk-class>java.lang.Integer</unknown-pk-class>
<field-name>kuchenid</field-name>
<column-name>kuchenid</column-name>
<jdbc-type>INTEGER</jdbc-type>
<sql-type>INTEGER</sql-type>
<auto-increment/>
</unknown-pk>
<entity-command name="hsqldb-fetch-key"></entity-command>
</entity>
Hier taucht auch unsere Primary-Key-Definition wieder auf.
Jetzt können wir die Bean auf den Server stellen. Dieser Prozess erzeugt die Datenbanktabelle. Im
Server-Log finden sich unter anderem diese Einträge, an der wir erkennen können wie die Tabelle erzeugt
wird und mit welchem Statements ein Einfügen oder Löschen erfolgt:
org.jboss.ejb.plugins.cmp.jdbc.keygen.JDBCHsqldbCreateCommand.KuchenSingle] Insert Entity SQL: INSERT INTO KUCHENSINGLE (name) VALUES (?)
org.jboss.ejb.plugins.cmp.jdbc.JDBCCommandFactory.KuchenSingle] entity-command: [commandName=hsqldb-fetch-key,commandClass=class org.jboss.ejb.plugins.cmp.jdbc.keygen.JDBCHsqldbCreateCommand,attributes={}]
org.jboss.ejb.plugins.cmp.jdbc.JDBCRemoveEntityCommand.KuchenSingle] Remove SQL: DELETE FROM KUCHENSINGLE WHERE kuchenid=?
org.jboss.ejb.plugins.cmp.jdbc.JDBCStartCommand.KuchenSingle] Executing SQL: CREATE TABLE KUCHENSINGLE (kuchenid INTEGER NOT NULL IDENTITY, name VARCHAR(256), CONSTRAINT PK_KUCHENSINGLE PRIMARY KEY (kuchenid))
org.jboss.ejb.plugins.cmp.jdbc.JDBCFindByPrimaryKeyQuery.KuchenSingle#findByPrimaryKey] SQL: SELECT t0_KuchenSingle.kuchenid FROM KUCHENSINGLE t0_KuchenSingle WHERE t0_KuchenSingle.kuchenid=?
org.jboss.ejb.plugins.cmp.jdbc.JDBCQueryManager.KuchenSingle] Added findByPrimaryKey query command for home interface
Datenbank-Adminstrations-Tool
Die Hypersonic-Datenbank bringt ein rudimentäres Administrationstool mit. Dieses erreichen wir, indem
wir bei laufendem Server auf die JMX-Konsole gehen (http://localhost:8080/jmx-console)
und in der Kategorie "jboss" die MBean "database=localDB,service=Hypersonic" auswählen.
Ab zur Funktion "startDatabaseManager" und auf "Invoke" klicken.
Es erscheint nur eine Seite mit einer schnöden Erfolgsmeldung, aber nach kurzer Zeit sollte sich eine Java-Anwendung, der
"HSQL Database Manager" öffnen. Hier sehen wir auf der linken Seite die Tabelle unserer Bean.
Application Client
Der Client muss die EJB-Jars referenzieren (siehe dazu Anleitung in den vorherigen Beispielen).
Wir bauen uns einen Client mit Swing-GUI. Dazu MUSS der Visual-Edit-Plugin installiert sein (siehe Installationsanleitung dieser
Beispiele).
Wir wählen in der J2EE-Hierarchie "Application Client Projects" -> "KuchenSingleClient" -> "appClientModule" aus
und gelangen per Rechtsklick -> "New" -> "Other..." in den Dialog zum Hinzufügen neuer Elemente. Wir
wählen "Java" -> "Swing" (dieser Punkt ist nur vorhanden wenn Visual Edit installiert ist !) -> "JFrame Visual Class" aus.
Package ist "com.knauf.ejb.kuchen", Klassenname ist "FrameKuchen". Erzeugte Kommentare können ebenfalls nicht schaden.
Jetzt sollte der GUI-Designer des Visual Edit-Plugins starten. Wir legen JList auf einem JScrollPane und drei Buttons
"Neu", "Bearbeiten" und "Löschen" an. Das Ergebnis sollte so aussehen:
Der Dialog "KuchenDialog" sollte so aussehen:
Die Main-Methode soll in einer Klasse "KuchenApplicationClient" liegen, die den FrameKuchen erzeugt und anzeigt.
Die EJB-Referenzen müssen im Deployment-Deskriptor des Clients deklariert werden. Leider
kann uns XDoclet hier nicht mehr helfen, wir müssen also selbst Hand anlegen.
Dazu die Datei "application-client.xml" öffnen und folgendes einfügen:
<ejb-ref>
<ejb-ref-name>ejb/KuchenSingle</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>com.knauf.ejb.kuchen.KuchenSingleHome</home>
<remote>com.knauf.ejb.kuchen.KuchenSingle</remote>
<ejb-link>KuchenSingle</ejb-link>
</ejb-ref>
Der Code für das JNDI-Lookup sieht so aus (im Konstruktor von "FrameKuchen"):
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
props.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");
props.setProperty("j2ee.clientName", "KuchenSingleClient");
InitialContext initialContext = new InitialContext(props);
Object homeref = initialContext.lookup(KuchenSingleHome.COMP_NAME);
this.kuchenHome = (KuchenSingleHome) PortableRemoteObject.narrow(homeref, KuchenSingleHome.class);
Wichtig: die Property j2ee.clientName
muss den Namen des Projekts haben !
Jetzt noch die Main class in "MANIFEST.MF" eintragen:
Manifest-Version: 1.0
Class-Path: KuchenSingleEJB.jar
Main-Class: com.knauf.ejb.kuchen.KuchenApplicationClient
Ausführen des Clients
Im Menü "Run" den Punkt "Run..." wählen.
In der Auswahl "Configurations" unter "Java Application" eine neue Konfiguration erstellen.
Die Einstellungen sollten so aussehen:
Zu beachten ist, dass vor der Anwendung der EJB-Server gestartet sein muss.
Version 1.0.1.0, Stand 27.11.2005
Historie:
1.0.0.0 (19.09.2005): Erstellt
1.0.0.1 (27.10.2005): EAR-Datei: Verweis auf zu aktivierenden XDoclet-Builder.
1.0.0.2 (01.11.2005): "KuchenFrame" in "FrameKuchen" umbenannt. Teile der Beschreibung und der Screenshots
waren noch von der alten Version mit der Main-Methode in "KuchenFrame" statt "KuchenApplicationClient".
1.0.1.0 (27.11.2005): Der Application Client verwendet EJB-Referenzen und jboss-client.xml