Beispiel: Container Managed Relationship (JBoss 4.0)
Inhalt
Anlegen der Application
Anlegen der Entity Bean "Kuchen"
Anlegen der Entity Bean "Zutat"
Hinzufügen der Relationships
Anlegen des Webclients
Datenbank
Beispiel für zwei Container Managed Entity Bean, auf die per Webclient zugegriffen wird.
Zwischen den beiden Beans besteht eine Container Managed Relationship.
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): KuchenZutat.ear
ACHTUNG: Nach dem Import XDoclet-Builder im EJB-Projekt aktivieren ! (Das Web-Projekt
verfügt über keine Servlets, deshalb kann WebDoclet dort nicht verwendet werden)
Aufbau des Beispieles
a) Entity Bean-Klasse für Kuchen mit Local-Interfaces.
b) Entity Bean-Klasse für Zutat mit Local-Interfaces.
c) Webclient
Anlegen der Application
Ein "EAR Application Project" mit dem Namen "KuchenZutat" erstellen.
Zu erzeugende Module definieren. Dieses Beispiel benötigt ein EJB-Projekt und
ein Anwendungsclientprojekt.
Im Project Explorer sollte es so aussehen (die angezeigte Fehlermeldung kommt daher dass
der Deploymentdeskriptor "ejb-jar.xml" gerade keine Bean-Einträge enthält und deshalb ungültig ist):
Anlegen der Entity Bean "Kuchen"
Es wird eine Enterprise Bean angelegt. Dazu im Project Explorer "EJB Projects" -> "KuchenZutatEJB" ->
wählen und im Contextmenü den Punkt "New" -> "Other" auswählen. Unter "EJB" finden
wir die "Enterprise Bean".
Da der Assistent keine Entity Beans unterstützt, legen wir wieder eine Session Bean an.
Sie heißt "KuchenBean" und liegt im Projekt "com.knauf.ejb.kuchenzutat".
Im letzten Schritt ändern wir das zu implementierende Interface von "javax.ejb.SessionBean"
auf "javax.ejb.EntityBean".
Den Rest der Arbeit müssen wir in der Bean-Klasse und mittels XDoclet erledigen.
-
Zuerst einmal ändern wir die XDoclet-Definition im Klassenkopf:
* @ejb.bean name="Kuchen"
* description="A session bean named Kuchen"
* display-name="Kuchen"
* jndi-name="Kuchen"
* type="CMP"
* transaction-type="Container"
* cmp-version="2.x"
* view-type="local"
* primkey-field="id"
* @ejb.pk
* class="java.lang.Integer"
* generate="false"
Wir möchten eine CMP-Entity-Bean haben. Den Primary Key werden wir hier selbst generieren und
den Typ "java.lang.Integer" verwenden (den XDoclet natürlich nicht als Klasse generieren soll).
Das XDoclet-Tag @ejb.pk
ist hier nicht nötig. Seeehr wichtig ist die Angabe das PrimaryKey-Felds
in @ejb.bean
!
-
Die Dummy-Methode "foo" wird entfernt.
-
Jetzt werden die Felder zugefügt: Name und ID (vom Typ java.lang.Integer).
/**Abrufen der Kuchen-ID
* @return Die Kuchen-ID
*
* @ejb.persistence column-name="id"
* @ejb.interface-method view-type="local"
* @ejb.pk-field
*/
public abstract Integer getId();
/**Setzen der Kuchen-Id.
* @param int_Id Die neue ID.
*
* @ejb.persistence column-name="id"
* @ejb.interface-method view-type="local"
* @ejb.pk-field
*/
public abstract void setId (Integer int_Id);
/**Abrufen des Kuchen-Namens.
* @return der Kuchen-Name.
*
* @ejb.persistence column-name="name" sql-type="VARCHAR"
* @ejb.interface-method view-type="local"
*/
public abstract String getName();
/**Setzen des Kuchen-Namens.
* @param str_Name der Name
*
* @ejb.persistence column-name="name" sql-type="VARCHAR"
* @ejb.interface-method view-type="local"
*/
public abstract void setName (String str_Name);
Man beachte das XDoclet-Tag @ejb.pk-field
in getId/setId !
In diesen beiden Methoden habe ich im @ejb.persistence
darauf verzichtet den Feldtyp anzugeben,
es hätte auch so aussehen können:
@ejb.persistence column-name="id" sql-type="INT"
- Die beiden ejbCreate-Methoden werden erzeugt, die jeweils als Rückgabewert einen Integer-Wert haben.
"ejbCreate" mit dem Kuchennamen als Parameter sieht so aus (für ejbCreate ohne Parameter analog):
/**Erzeugen eines Kuchen-Objekts, wobei der Name angegeben wird. Der Key wird automatisch erzeugt.
* @param str_Name Der Name des Kuchens.
* @return Die ID des Kuchens.
* @exception CreateException Fehler beim Erzeugen.
*
* @ejb.create-method view-type="local"
*/
public Integer ejbCreateByName (String str_Name) throws CreateException
{
KuchenBean.logger.info("ejbCreateByName: " + str_Name);
this.setName(str_Name);
//Key erzeugen:
this.setId ( this.ejbHomeGetNextId() );
//return null;
return this.getId();
}
Natürlich müssen die zugehörigen ejbPostCreate-Methoden ebenfalls deklariert werden.
- Die Deklaration der Finder-Methode wird im Klassen-Kopf zugefügt:
* @ejb.finder
* signature="java.util.Collection findAll()"
* query="select Object(o) from Kuchen o order by o.name"
- Die Select-Methode für das Ermitteln der maximalen ID wird angelegt:
/**Ermitteln der maximalen Kuchen-ID in Datenbank
* Der EJB-QL-Befehl wird per Deploytool festgelegt.
* (select max (....)
* @return Maximale ZutatID aus Datenbank.
*
* @ejb.select query="select max (k.id) from Kuchen k"
*/
public abstract Integer ejbSelectMaxId() throws javax.ejb.FinderException;
- Zu guter letzt noch die Hilfsmethode "getNextId" zufügen, die aus den ejbCreate-Methoden
aufgerufen wird und die ejbSelect-Methode aufruft.
Anlegen der Entity-Bean "Zutat"
Diese Bean kopieren wir am besten aus "KuchenBean" und ersetzen überall "Kuchen" durch "Zutat"
(auch in EJB-QL !). Wenn wir das Kopieren innerhalb des Project Explorer durchführen ("KuchenBean"
wählen -> Copy -> Paste im Package com.knauf.ejb.kuchenzutat
, dann fordert Eclipse
uns zur Eingabe eines neuen Namens auf und ersetzt auch den Klassennamen im Code.
Außerdem wird ein Feld "Menge" vom Typ java.lang.String
zugefügt.
Hinzufügen der Relationships
In "KuchenBean" fügen wir die folgenden beiden Methoden zu:
/**Abrufen aller Zutaten dieses Kuchens.
* @return Collection von Zutat-Objekten.
* @ejb.interface-method view-type="local"
* @ejb.relation
* name="KuchenZutaten"
* role-name="kuchen-hat-zutaten"
*/
public abstract Collection getZutaten();
/**Setzen der kompletten Zutaten-Liste des Kuchens.
* Wird vom Container übernommen.
* @param coll_Zutaten Liste der Zutaten.
*
* @ejb.interface-method view-type="local"
*/
public abstract void setZutaten (Collection coll_Zutaten);
Außerdem werden die beiden Hilfsmethoden "addZutat" und "getZutatenListe" zugefügt, beide
sollen in das Local-Interface eingebaut werden.
In der Zutat-Bean werden getKuchen und setKuchen eingebaut.
/**Abrufen des Kuchens, zu dem die Zutat gehört.
* @return Den Kuchen der Zutat.
*
* @ejb.interface-method view-type="local"
* @ejb.relation
* name="KuchenZutaten"
* role-name="zutat-hat-kuchen"
* cascade-delete="yes"
* @jboss.relation fk-constraint="true"
* related-pk-field="id"
* fk-column="kuchen_fk"
*/
*/
public abstract KuchenLocal getKuchen();
/**Setzen des Kuchens, zu dem die Zutat gehört.
* @param kuchen Der Kuchen der Zutat.
*
* @ejb.interface-method view-type="local"
*/
public abstract void setKuchen(KuchenLocal kuchen);
Hierzu wichtig:
-Der Name der Relation (hier: "KuchenZutaten") muss auf beiden Seiten der Relation gleich sein.
-Die Deklaration ist so zu lesen: bei der Methode der ZutatBean wird deklariert wie die Relation zur ZutatBean aus Sicht
des Kuchens sich verhalten soll (es gibt viele Zutaten (Many
), beim Löschen des Kuchens soll die Zutat gelöscht
werden).
-Auf der Zutat-Seite muss eine @jboss-relation
deklariert werden, da der Server sonst diese
Fehlermeldung ausspuckt (für beide an der Relationship beteiligten Beans):
21:20:02,250 WARN [ServiceController] Problem starting service jboss.j2ee:jndiName=KuchenLocal,service=EJB
org.jboss.deployment.DeploymentException: Atleast one role of a foreign-key mapped relationship must have key fields (or <primkey-field> is missing from ejb-jar.xml): ejb-relation-name=KuchenZutaten
...
Mittels der JBoss-Relation geben wir an auf welches Primary-Key-Feld der KuchenBean sich die Relation "KuchenZutat" bezieht
und wie das Feld in der Tabelle "Zutat" heißen soll.
Auf der Gegenseite (im Kuchen) ist eine solche Angabe nicht erlaubt, da die KuchenBean kein Forein-Key-Feld auf die ZutatBean hat.
In "ejb-jar.xml" sieht das dann so aus:
<relationships >
<ejb-relation >
<ejb-relation-name>KuchenZutaten</ejb-relation-name>
<ejb-relationship-role >
<ejb-relationship-role-name>zutat-hat-kuchen</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<cascade-delete/>
<relationship-role-source >
<ejb-name>Zutat</ejb-name>
</relationship-role-source>
<cmr-field >
<cmr-field-name>kuchen</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role >
<ejb-relationship-role-name>kuchen-hat-zutaten</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source >
<ejb-name>Kuchen</ejb-name>
</relationship-role-source>
<cmr-field >
<cmr-field-name>zutaten</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
</relationships>
Hier lohnt es einen Blick in die Container-spezifische Datei "jboss-cmp.xml" zu werfen:
<relationships>
<ejb-relation>
<ejb-relation-name>KuchenZutaten</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>zutat-hat-kuchen</ejb-relationship-role-name>
<fk-constraint>true</fk-constraint>
<key-fields/>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>kuchen-hat-zutaten</ejb-relationship-role-name>
<key-fields>
<key-field>
<field-name>id</field-name>
<column-name>kuchen_fk</column-name>
</key-field>
</key-fields>
</ejb-relationship-role>
</ejb-relation>
</relationships>
In der Relationship-Rolle "kuchen-hat-zutat" ist der Name der Foreign-Key-Spalte in der Tabelle "Zutat" und
der Name der Primary-Key-Spalte in der referenzierten Tabelle deklariert.
Anlegen des Webclients
Der Webclient muss die EJB-JARs referenzieren. Dazu in die Eigenschaften des Webmoduls "KuchenZutatWeb"
wechseln und unter "J2EE Module Depdencies" das EJB-JAR wählen.
EJB-Verweise festlegen:
Da wir hier kein Servlet verwenden können wir uns den EJB-Verweis nicht per XDoclet generieren lassen.
Also in "Dynamic Web Projects" -> "KuchenZutatWeb" -> "WebContent" -> "WEB-INF" -> "web.xml" gehen und
folgendes einfügen:
<ejb-local-ref>
<ejb-ref-name>ejb/Zutat</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>com.knauf.ejb.kuchenzutat.ZutatLocalHome</local-home>
<local>com.knauf.ejb.kuchenzutat.ZutatLocal</local>
<ejb-link>Zutat</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/Kuchen</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>com.knauf.ejb.kuchenzutat.KuchenLocalHome</local-home>
<local>com.knauf.ejb.kuchenzutat.KuchenLocal</local>
<ejb-link>Kuchen</ejb-link>
</ejb-local-ref>
Es müssen vier JSP-Seiten "Kuchen.jsp", "KuchenEdit.jsp", "KuchenZutaten.jsp", "KuchenZutatEdit.jsp" zugefügt werden.
Nach dem Deploy auf den Server ist die Anwendung unter
http://localhost:8080/KuchenZutatWeb/Kuchen.jsp zu erreichen.
Datenbank
In der Datenbank sieht das so aus (Hypersonic-Databasemanager über JMX-Console starten):
"Kuchen"-Tabelle:
"Zutat"-Tabelle:
Besonders zu beachten ist die automatisch generierte Spalte mit dem Foreign Key-Feld zum Kuchen.
Version 1.0.1.2, Stand 17.11.2005
Historie:
1.0.0.0 (11.10.2005): Erstellt
1.0.0.1 (27.10.2005): EAR-Datei: Verweis auf zu aktivierenden XDoclet-Builder.
1.0.1.0 (31.10.2005): Relationship war falsch (unidirektional) deklariert. EJB-Refs in web.xml enthielten
falsche Interface-Namen.
1.0.1.1 (10.11.2005): Code in KuchenZutaten.jsp aufgeräumt.
1.0.1.2 (17.11.2005): Alle ejbCreate-Methoden geben jetzt (gemäß EJB-Spezifikation) null
zurück.
Inhalt zugefügt.