Beispiel: Entity Relationships
Inhalt
Anlegen der Application
Anlegen der Entity Bean "Kuchen"
Anlegen der Entity Bean "Zutat"
Hinzufügen der Relationships
persistence.xml
Anlegen der Session Bean "KuchenZutatWorker"
Anlegen des Webclients
Datenbank
Ohne Annotations
Troubleshooting
Beispiel für zwei Container Managed Entity Bean, auf die per Webclient zugegriffen wird.
Zwischen den beiden Beans besteht eine Container Managed Relationship.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet
man im Stateless-Beispiel): KuchenZutat.ear
Aufbau des Beispieles
a) Entity Bean-Klasse für Kuchen.
b) Entity Bean-Klasse für Zutat.
c) KuchenZutatWorkerBean mit Local-Interfaces.
d) Webclient
Anlegen der Application
Ein "Enterprise Application Project" mit dem Namen "KuchenZutat" erstellen.
Zu erzeugende Module definieren. Dieses Beispiel benötigt ein EJB-Projekt und
ein Anwendungsclientprojekt.
Jetzt noch die Deployment-Deskriptoren "application.xml", "ejb-jar.xml" und "web.xml" auf den Stand von JavaEE5 bringen.
Anlegen der Entity Bean "Kuchen"
Es wird eine Enterprise Bean angelegt. Dazu eine neue Klasse namens "KuchenBean" im Package "de.fhw.swtvertiefung.knauf.kuchenzutat"
erstellen. Die Klasse soll das Interface "java.io.Serializable" implementieren.
Jetzt codieren wir die Bean-Klasse. Fett markiert sind Entity-Bean-relevante Elemente:
@Entity()
@NamedQuery(name="findAllKuchen", query="select o from KuchenBean o")
public class KuchenBean implements Serializable
{
private Integer intId;
private String strName;
@Column()
@Id ()
@GeneratedValue ()
public Integer getId()
{
return this.intId;
}
public void setId(Integer int_Id)
{
this.intId = int_Id;
}
@Column()
public String getName()
{
return this.strName;
}
public void setName(String str_Name)
{
this.strName = str_Name;
}
}
Die Bean enthält eine generierte ID sowie ein Feld "Name". Es gibt eine Named Query um
alle Kuchen aus der Datenbank zu holen.
Anlegen der Entity-Bean "Zutat"
Wir fügen eine weitere Java-Klasse "ZutatBean" zu.
Der Code sieht so aus:
@Entity()
public class ZutatBean implements Serializable
{
private Integer intId;
private String strZutatName;
private String strMenge;
public ZutatBean()
{
}
@Column()
@Id ()
@GeneratedValue ()
public Integer getId()
{
return this.intId;
}
public void setId(Integer int_Id)
{
this.intId = int_Id;
}
@Column()
public String getZutatName()
{
return this.strZutatName;
}
public void setZutatName(String str_ZutatName)
{
this.strZutatName = str_ZutatName;
}
@Column()
public String getMenge()
{
return this.strMenge;
}
public void setMenge(String str_Menge)
{
this.strMenge = str_Menge;
}
}
Sie enthält eine generierte ID sowie zwei Felder "Name" und "Menge.
Hinzufügen der Relationships
In "KuchenBean" fügen wir die folgendes zu:
private Collection<ZutatBean> collZutaten = new ArrayList<ZutatBean>();
@OneToMany(mappedBy="kuchen", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
public Collection getZutaten()
{
return this.collZutaten;
}
public void setZutaten (Collection coll_Zutaten)
{
this.collZutaten = coll_Zutaten;
}
Die "OneToMany"-Annotation gibt an welche Property in der ZutatBean das Gegenstück der Relationship
darstellt. Der CascadeType
gibt an ob Speichern- oder Lösch-Aufrufe auf dem aktuellen Objekt zur Zutat
weiterkaskadiert werden. Der FetchType
schließlich gibt an ob beim Laden einer KuchenBean
direkt die Zutaten geholt werden sollen oder ob diese erst nachträglich geholt werden sollen.
Die ZutatBean erhält das Gegenstück des Mappings:
private KuchenBean kuchen = null;
@ManyToOne ()
public KuchenBean getKuchen()
{
return this.kuchen;
}
public void setKuchen (KuchenBean kuchen)
{
this.kuchen = kuchen;
}
Hier ist außer einer "ManyToOne"-Deklaration nichts weiter zu tun. Zu beachten ist dass ich bei der
Relationship weder CascadeType
noch FetchType
gesetzt habe.
Ein Kasadieren von Datenänderungen (Löschen der Zutat) würde in unserem Fall keinen Sinn ergeben.
Der nicht gesetzte FetchType
hat Auswirkungen in der Session-Bean, denn wir können den Kuchen zur
Zutat dadurch nur abrufen solange die Bean noch unter Verwaltung des Entity-Managers steht.
persistence.xml
Für Entity Beans muss eine Persistence Unit deklariert werden. Dies geschieht über eine Datei "persistence.xml" im Unterverzeichnis
"META-INF" des EJB-Projekts.
Sie hat diesen Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="kuchenZutatPersistenceUnit">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<!-- Setzen dieser Property aktiviert das automatische Tabellen-Generieren und Löschen beim Deploy! -->
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<!-- SQL-Logging einschalten: -->
<property name="hibernate.show_sql" value="true"></property>
</properties>
</persistence-unit>
</persistence>
Es wird eine "persistence-unit" namens "kuchenZutatPersistenceUnit" deklariert. Sie ist verbunden mit einer JDBC-Datenquelle des
Servers die im JNDI unter dem Namen "java:/DefaultDS" abgelegt ist und auf die JBoss-interne Hypersonic-Datenbank zeigt.
Die Property "hibernate.hbm2ddl.auto" ist JBoss-spezifisch und legt fest dass Datenbanktabelle beim Deploy einer Bean erzeugt
und beim Undeploy wieder gelöscht werden sollen. Ohne diesen Parameter müssten wir die Datenbanktabellen von Hand anlegen.
"hibernate.show_sql" sorgt dafür dass die erzeugten SQL-Statements im Log und auf der Server-Konsole angezeigt werden, sinnvoll für die Fehlersuche.
Anlegen der Session Bean "KuchenZutatWorker"
Es wird eine SessionBean "KuchenZutatWorkerBean" zugefügt, die diese Methoden enthält:
@Stateless
public class KuchenZutatWorkerBean
{
@PersistenceContext(unitName="kuchenZutatPersistenceUnit")
EntityManager entityManager = null;
public void saveKuchen (KuchenBean kuchen)
{
this.entityManager.merge(kuchen);
}
public List getKuchen()
{
Query query = this.entityManager.createNamedQuery("findAllKuchen");
List<KuchenBean> listKuchen = query.getResultList();
return listKuchen;
}
public KuchenBean findKuchenById(Integer int_Id)
{
KuchenBean kuchen = this.entityManager.find(KuchenBean.class, int_Id);
return kuchen;
}
public void deleteKuchen (KuchenBean kuchen)
{
//Zuerst den Kuchen aus der Datenbank holen:
kuchen = this.entityManager.find (KuchenBean.class, kuchen.getId() );
this.entityManager.remove(kuchen);
}
public ZutatBean findZutatById(Integer int_Id)
{
ZutatBean zutat = this.entityManager.find(ZutatBean.class, int_Id);
//Den Kuchen einmal abrufen solange die Bean unter
//Container-Verwaltung ist.
zutat.getKuchen();
return zutat;
}
public void deleteZutat (ZutatBean zutat)
{
//Zuerst einmal die Zutat im EntityManager holen:
zutat = this.entityManager.find (ZutatBean.class, zutat.getId() );
//Hier müßte eine Prüfung erfolgen ob die Zutat in der Datenbank noch existiert !
//Jetzt wird es knifflig: wir müssen den Kuchen der Zutat holen,
//und aus der Zutat-Collection des Kuchen die Zutat entfernen.
//Grund scheint zu sein dass der EntityManager ansonsten noch die
//Zutat im Kuchen hält und dabei in einen Fehlerzustand läuft.
KuchenBean kuchenbean = zutat.getKuchen();
//Zutat wegwerfen:
kuchenbean.getZutaten().remove(zutat);
//Ein Speichern des Kuchens ist nicht nötig (bzw. bewirkt in
//unserem Falle nichtS)
//this.entityManager.merge(zutat);
//Jetzt endlich dürfen wir die Zutat löschen.
this.entityManager.remove(zutat);
}
public void saveZutat (ZutatBean zutat)
{
this.entityManager.merge(zutat);
}
}
Besonders interessant ist hier "deleteZutat", denn ein "entitymanager.remove(zutat)" alleine führte bei mir zu Fehlern,
erst das vorherige Entfernen aus dem Kuchen verschaffte Abhilfe (siehe Abschnitt Troubleshooting).
Die Bussiness-Methoden werden in ein local Interface "KuchenZutatWorkerLocal" extrahiert, die Bean muss dieses
Interface implementieren.
Das Interface erhält die Annotation "@Local".
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:
In "KuchenZutatWeb" -> "WebContent" -> "WEB-INF" -> "web.xml" gehen und folgendes einfügen:
<ejb-local-ref>
<ejb-ref-name>ejb/KuchenZutatWorkerLocal</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home>java.lang.Object</local-home>
<local>de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenZutatWorkerLocal</local>
</ejb-local-ref>
Außerdem muss die EJB-Referenz in einer Datei "WEB-INF\jboss-web.xml" angegeben werden:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.4//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_4_0.dtd">
<jboss-web>
<context-root>KuchenZutatWeb</context-root>
<ejb-local-ref>
<ejb-ref-name>ejb/KuchenZutatWorkerLocal</ejb-ref-name>
<local-jndi-name>KuchenZutat/KuchenZutatWorkerBean/local</local-jndi-name>
</ejb-local-ref>
</jboss-web>
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.
Achtung: Die Bedienung ist mehr als hakelig, wenn man von einer Unterseite aus zur Hauptseite ("Kuchen.jsp") zurückgeht
wird man häufig nicht die aktualisierte Kuchen-Liste sehen, in diesem Fall hilft es die Seite im Browser zu aktualisieren (F5).
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.
Ohne Annotations
"ejb-jar.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar id="ejb-jar_ID" version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
<display-name>KuchenZutatEJB</display-name>
<enterprise-beans>
<session>
<description>
<![CDATA[Stateless Session Bean für das Arbeiten mit Kuchen und Zutaten.]]>
</description>
<display-name>KuchenZutatWorkerBean</display-name>
<ejb-name>KuchenZutatWorkerBean</ejb-name>
<local>de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenZutatWorkerLocal</local>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenZutatWorkerBean</ejb-class>
<session-type>Stateless</session-type>
<!--EntityManager-Injection -->
<persistence-context-ref>
<persistence-context-ref-name>KuchenZutatPersistenceUnitRef</persistence-context-ref-name>
<persistence-unit-name>kuchenZutatPersistenceUnit</persistence-unit-name>
<injection-target>
<injection-target-class>
de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenZutatWorkerBean
</injection-target-class>
<injection-target-name>entityManager</injection-target-name>
</injection-target>
</persistence-context-ref>
</session>
<entity>
<description>
<![CDATA[Entity Bean für einen einzelnen Kuchen.]]>
</description>
<display-name>KuchenBean</display-name>
<ejb-name>KuchenBean</ejb-name>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>false</reentrant>
</entity>
<entity>
<description>
<![CDATA[Entity Bean für eine einzelne Zutat.]]>
</description>
<display-name>ZutatBean</display-name>
<ejb-name>ZutatBean</ejb-name>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchenzutat.ZutatBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>false</reentrant>
</entity>
</enterprise-beans>
</ejb-jar>
Es gibt keine Neuerungen im Vergleich zum KuchenSimple-Beispiel.
"orm.xml" enthält die Angaben über das Mapping:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
<named-query name="findAllKuchen">
<query>select o from KuchenBean o</query>
</named-query>
<entity class="de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenBean" access="PROPERTY"
metadata-complete="true">
<table name="KUCHENBEAN"></table>
<attributes>
<id name="id">
<column name="ID" />
<generated-value />
</id>
<basic name="name">
<column name="NAME" />
</basic>
<one-to-many name="zutaten" mapped-by="kuchen" fetch="EAGER"
target-entity="de.fhw.swtvertiefung.knauf.kuchenzutat.ZutatBean">
<cascade>
<cascade-all />
</cascade>
</one-to-many>
</attributes>
</entity>
<entity class="de.fhw.swtvertiefung.knauf.kuchenzutat.ZutatBean" access="PROPERTY"
metadata-complete="true">
<table name="ZUTATBEAN"></table>
<attributes>
<id name="id">
<column name="ID" />
<generated-value />
</id>
<basic name="zutatName">
<column name="ZUTATNAME" />
</basic>
<basic name="menge">
<column name="MENGE" />
</basic>
<many-to-one name="kuchen"
target-entity="de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenBean">
</many-to-one>
</attributes>
</entity>
</entity-mappings>
Die XML-Elemente für das Mapping entsprechen weitgehend denen der Annotation. Einzige Besonderheit
ist die Definition der "target-entity", also der Entity die das Ziel der Relationship ist.
Die modifizierte Version des Projekts gibt es hier: KucheZutatNoAnnotation.ear.
ACHTUNG 1: Dieses Projekt kann nicht neben dem obigen KuchenZutat-Beispiel existieren !
ACHTUNG 2: Ein Re-Import der EAR-Datei klappt nur wenn in ejb-jar.xml das Element "persistence-context-ref"
auskommentiert ist ! Ansonsten zeigt Eclipse eine Fehlermeldung, ohne sich allerdings über die Details auszulassen.
Dieses Element muss nach dem Import wieder einkommentiert werden !
Troubleshooting
Hier werden ein paar Fehler beschrieben in die ich beim Programmieren gelaufen bin ;-)
- In KuchenBean sieht die Methode "getZutaten" so aus (es wird der Default-FetchType "Lazy" verwendet):
@OneToMany(mappedBy="kuchen", cascade=CascadeType.ALL)
public Collection<ZutatBean> getZutaten()
{
...
Beim Abrufen der Zutatenliste eines Kuchens (nach dem Aufruf von "KuchenZutatWorkerBean.findKuchenById") gibt es diese Fehlermeldung
auf der JSP-Seite "KuchenEdit.jsp" (beim Bearbeiten eines Kuchens):
21:05:31,734 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenBean.zutaten, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: de.fhw.swtvertiefung.knauf.kuchenzutat.KuchenBean.zutaten, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:343)
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:249)
at org.apache.jsp.KuchenEdit_jsp._jspService(KuchenEdit_jsp.java:111)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:97)
Lösung 1: FetchType auf FetchType.EAGER
setzen. Dies kann allerdings in manchen Fällen nicht sinnvoll sein, z.B. wenn
viele Objekte miteinander verkettet sind und man dadurch die halbe Datenbank einlesen würde ;-).
Lösung 2: Bevor die KuchenBean detached wird (also nach dem Ende von "KuchenZutatWorkerBean.findKuchenById") werden in einer Schleife explizit
die Zutaten abgerufen:
Collection collZutaten = kuchen.getZutaten();
logger.info ("Anzahl Zutaten: " + collZutaten.size());
Wichtig hierbei ist dass wir irgendetwas mit der Zutaten-Collection anstellen damit der Container
die auch wirklich abruft. Im Beispiel reicht es die size()
-Methode für eine Logausgabe abzurufen.
- In KuchenBean sieht die Methode "getZutaten" so aus (es ist kein
CascadeType
gesetzt):
@OneToMany(mappedBy="kuchen", fetch=FetchType.EAGER)
public Collection getZutaten()
{
...
Das führt beim Aufruf von "KuchenZutatWorkerBean.saveKuchen(kuchen)" für einen Kuchen mit geänderten Zutaten zu folgender
Meldung:
javax.ejb.EJBException: java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: de.fhw.swtvertiefung.knauf.kuchenzutat.ZutatBean
org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:69)
org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:83)
org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:197)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.stateless.StatelessInstanceInterceptor.invoke(StatelessInstanceInterceptor.java:62)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.aspects.security.AuthenticationInterceptor.invoke(AuthenticationInterceptor.java:78)
org.jboss.ejb3.security.Ejb3AuthenticationInterceptor.invoke(Ejb3AuthenticationInterceptor.java:131)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:47)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.asynchronous.AsynchronousInterceptor.invoke(AsynchronousInterceptor.java:106)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.stateless.StatelessContainer.localInvoke(StatelessContainer.java:211)
org.jboss.ejb3.stateless.StatelessLocalProxy.invoke(StatelessLocalProxy.java:79)
$Proxy225.saveKuchen(Unknown Source)
org.apache.jsp.KuchenZutatEdit_jsp._jspService(KuchenZutatEdit_jsp.java:95)
Grund ist dass Entity-Manager-Operationen auf den Kuchen sich nicht auf die Zutat auswirken und es deshalb
zu ungültigen Mischzuständen kommt. Einfachste Lösung ist es den CascadeType auf CascadeType.ALL
zu stellen, wobei das nicht in allen Fällen sinnvoll ist. Zum Beispiel ist ein kaskadierendes Löschen nicht immer
gewünscht.
- Ausnahmsweise mal keine Exception aber trotzdem falsch ;-):
In KuchenBean sieht die Methode "getZutaten" so aus (mappedBy
-Attribut nicht gesetzt):
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
public Collection getZutaten()
{
...
Die Anwendung scheint zu funktionieren, allerdings wird eine n:m-Mapping-Tabelle "KUCHENBEAN_ZUTATBEAN" angelegt.
Die zusätzliche Spalte "KUCHEN_ID" in der Zutaten-Tabelle ist ebenfalls vorhanden und wird vom Container gepflegt.
- "KuchenZutatWorkerBean.deleteZutat" enthält nicht den Code aus obigem Beispiel sondern sieht so aus:
public void deleteZutat (ZutatBean zutat)
{
zutat = this.entityManager.find (ZutatBean.class, zutat.getId() );
this.entityManager.remove(zutat);
}
Das führt zu dieser Exception:
java.lang.RuntimeException: org.jboss.tm.JBossRollbackException: Unable to commit, tx=TransactionImpl:XidImpl[FormatId=257, GlobalId=...../293, BranchQual=, localId=293] status=STATUS_NO_TRANSACTION; - nested throwable: (javax.persistence.EntityNotFoundException: deleted entity passed to persist: [de.fhw.swtvertiefung.knauf.kuchenzutat.ZutatBean#])
org.jboss.aspects.tx.TxPolicy.handleEndTransactionException(TxPolicy.java:198)
org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:180)
org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:87)
org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:197)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.stateless.StatelessInstanceInterceptor.invoke(StatelessInstanceInterceptor.java:62)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.aspects.security.AuthenticationInterceptor.invoke(AuthenticationInterceptor.java:78)
org.jboss.ejb3.security.Ejb3AuthenticationInterceptor.invoke(Ejb3AuthenticationInterceptor.java:131)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:47)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.asynchronous.AsynchronousInterceptor.invoke(AsynchronousInterceptor.java:106)
org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
org.jboss.ejb3.stateless.StatelessContainer.localInvoke(StatelessContainer.java:211)
org.jboss.ejb3.stateless.StatelessLocalProxy.invoke(StatelessLocalProxy.java:79)
$Proxy246.deleteZutat(Unknown Source)
org.apache.jsp.KuchenZutatEdit_jsp._jspService(KuchenZutatEdit_jsp.java:155)
Die Lösung für dieses Problem ist der obenstehenden Implementierung der KuchenZutatWorkerBean zu entnehmen:
Zuerst wird die Zutat aus ihrem zugehörigen Kuchen entfernt, dann erst wird sie gelöscht.
Ein Speichern des Kuchens nachdem die Zutat entfernt wurde ist übrigens nicht nötig.
Stand 28.01.2007
Historie:
25.09.2006: Erstellt
26.09.2006: Beispiel ohne Annotations, Abschnitt "persistence.xml"
08.11.2006: Beispiel ohne Annotations: unnötiges Element "primkey-field" aus ejb-jar.xml geworfen.
28.01.2007: Falsche EJB-Referenz in web.xml korrigiert