Beispiel: N:M-Relationship zweiter Entity Beans
Inhalt:
Anlegen der Application
Anlegen der Entity Bean "KuchenNM"
Anlegen der Entity-Bean "ZutatNM"
Hinzufügen der Relationship
persistence.xml
Anlegen der Session Bean "KuchenZutatNMWorker"
Anlegen des Webclients
Blick in die Datenbank
Ohne Annotations
Beispiel für zwei Entity Bean, auf die per Webclient zugegriffen wird.
Zwischen den beiden Beans besteht eine Relationship, der Primary Key ist Container-erzeugt.
Änderung im Vergleich zu den vorherigen Beispielen: Kuchen und Zutat werden im
Webclient separat eingegeben, die n:m-Verknüpfung ist eine Zuordnung von Kuchen zu Zutat.
Die Information "Menge" entfällt, da wir ansonsten die Relationship um weitere Fehler erweitern
müßten und dafür wahrscheinlich eine eigene Zuordnungs-Bean nötig wäre.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet
man im Stateless-Beispiel): KuchenZutatNM.ear
Aufbau des Beispieles
a) Entity Bean-Klasse für Kuchen mit Local-Interfaces.
b) Entity Bean-Klasse für Zutat mit Local-Interfaces.
c) Session Bean für das Arbeiten mit Zutaten und Kuchen.
d) Webclient
Anlegen der Application
Ein leeres "EAR Application Project" mit dem Namen "KuchenZutatNM" erstellen. Dabei die Option "Generate Deployment Descriptor" setzen.
Zu erzeugende Module zufügen. Dieses Beispiel benötigt ein EJB-Projekt und ein Webprojekt. Dabei jeweils die Option "Generate Deployment Descriptor" setzen.
Anlegen der Entity Bean "KuchenNM"
Wir legen eine Klasse namens "KuchenNMBean" im Package "de.fhw.swtvertiefung.knauf.kuchenzutatnm" an,
die das Interface java.io.Serializable
implementiert.
Der Code der Klasse mit Annotations (noch ohne Relationship-Felder) sieht so aus:
@Entity()
@NamedQuery(name="findAllKuchen", query="select o from KuchenNMBean o")
public class KuchenNMBean implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer intId;
private String strName;
public KuchenNMBean()
{
}
@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;
}
}
Anlegen der Entity-Bean "ZutatNM"
Die ZutatNM-Bean sieht fast genauso aus wie die KuchenNM-Bean.
Einziger Unterschied: Die Property "name" heißt hier "zutatName".
@Entity()
@NamedQuery(name="findAllZutaten", query="select o from ZutatNMBean o")
public class ZutatNMBean implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer intId;
private String strZutatName;
public ZutatNMBean()
{
}
@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;
}
}
Hinzufügen der Relationship
In "KuchenBean" fügen wir eine Membervariable und zwei Methoden zu:
private Collection<ZutatBean> collZutaten = new ArrayList<ZutatBean>();
@ManyToMany(mappedBy="kuchen", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY)
public Collection<ZutatBean> getZutaten()
{
return this.collZutaten;
}
public void setZutaten (Collection<ZutatBean> coll_Zutaten)
{
this.collZutaten = coll_Zutaten;
}
Über den CascadeType müssen wir uns diesmal mehr Gedanken machen: ein kaskadierendes Löschen soll verboten sein,
denn eine Zutat kann auch ohne Kuchen existieren. Deshalb kaskadieren wir alle Operationen außer dem Löschen weiter
(Merge, Persist und Refresh). Der FetchType sollte hier nicht "Eager" sondern "Lazy" sein (denn wir wollen möglichst
wenig Daten zusammen mit dem angeforderten Objekt mitladen).
In der Zutat-Bean wird die andere Seite der Relation
deklariert:
private Collection<KuchenBean> collKuchen = new ArrayList<KuchenBean>();
@ManyToMany(cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY)
public Collection<KuchenBean> getKuchen()
{
return this.collKuchen;
}
public void setKuchen (Collection<KuchenBean> coll_Kuchen)
{
this.collKuchen = coll_Kuchen;
}
Hier müssen wir kein Attribut "mappedBy" in der Annotation angegeben.
Den FetchType habe ich auf "Lazy" gesetzt, denn wenn wir den auf beiden Seite der Relation auf "Eager" setzen würden,
dann würde beim Abrufen eines Kuchens die Liste seiner Zutaten geholt, pro Zutat wiederum die Liste der Kuchen für
die die Zutat verwendet wird, und für jeden dieser Kuchen wiederum die Zutaten. Dadurch würde im schlimmsten Fall
die gesamte Datenbank bei einem Zugriff eingelesen werden.
@ManyToMany
und die @JoinTable
-Annotation:
Mittels der
@JoinTable
-Annotation kann man kontrollieren, wie die Mapping-Tabelle sowie deren Spalten heißen sollen.
Hierbei ist es bei bidirektionalen Relationships wichtig, wohin man sie setzt! Sie darf auf keinen Fall auf die Seite der Relationship, in der das "mappedBy"-Attribut
deklariert ist.
Für obiges Beispiel wäre die korrekte Lösung:
KuchenBean:
private Collection<ZutatBean> collZutaten = new ArrayList<ZutatBean>();
@ManyToMany(mappedBy="kuchen", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY)
public Collection<ZutatBean> getZutaten()
{
return this.collZutaten;
}
ZutatBean:
private Collection<KuchenBean> collKuchen = new ArrayList<KuchenBean>();
@ManyToMany(cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY)
@JoinTable (name="KUCHENZUTATMAPPING",
joinColumns={@JoinColumn(name="KUCHENID") },
inverseJoinColumns={@JoinColumn(name="ZUTATID") })
public Collection<KuchenBean> getKuchen()
{
return this.collKuchen;
}
Setzt man den
@JoinTable
in die KuchenBean (also dort, wo das "mappedBy" deklariert ist), so wird diese Deklaration einfach ignoriert und
der JBoss generiert Default-Namen für die Mappingtabelle!
In einem Nebensatz findet man einen dezenten Hinweis auf dieses Verhalten in der
Hibernate-Doku.
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 xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="kuchenZutatNMPersistenceUnit">
<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 "kuchenZutatNMPersistenceUnit" 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 "KuchenZutatNMWorker"
Es wird eine SessionBean "KuchenZutatNMWorkerBean" zugefügt, die diese Methoden enthält
(ich möchte mit besonderem Stolz darauf hinweisen, dass ich hier versucht habe, die Fehlerbehandlung weitgehend sinnvoll zu gestalten ;-) ):
@Stateless
public class KuchenZutatNMWorkerBean implements KuchenZutatNMWorkerLocal
{
@PersistenceContext(unitName="kuchenZutatNMPersistenceUnit")
protected EntityManager entityManager = null;
Die folgenden Methoden sind mit denen des KuchenZutat-Beispiel weitgehend identisch.
Besonderheit ist in "findKuchenById" bzw. "findZutatById" das explizite Abrufen der Kuchen-/Zutaten-Liste.
Grund ist dass der FetchType auf "LAZY" steht, die Relation also nicht direkt beim Laden des Objekts eingelesen
wird. Deshalb MUSS die Liste der Kuchen/Zutaten abgerufen werden solange die Bean noch nicht detached ist.
Beim Abrufen reicht es nicht die Collection über "getZutaten"/"getKuchen" zu holen, es muss irgendeine Operation
ausgeführt werden die explizit die Collection einliest. Im Beispiel ist das das Abrufen der Anzahl der Datensätze
für eine Logger-Ausgabe. Ohne dieses Stück Code wird ein Zugriff im Webclient eine Exception auslösen.
public void saveKuchen (KuchenNMBean kuchen)
{
kuchen = this.entityManager.merge(kuchen);
}
public List<KuchenNMBean> getKuchen()
{
Query query = this.entityManager.createNamedQuery("findAllKuchen");
List<KuchenNMBean> listKuchen = query.getResultList();
return listKuchen;
}
public KuchenNMBean findKuchenById(Integer int_Id)
{
//Die Zutat im EntityManager laden.
//Hier mit "find" arbeiten, da bei ungültiger ID "null" zurückkommen soll.
KuchenNMBean kuchen = this.entityManager.find(KuchenNMBean.class, int_Id);
//Falls etwas gefunden wurde, dann die Relationship einlesen.
if (kuchen != null)
{
//Da der FetchType der ManyToMany-Relation auf LAZY gesetzt ist müssen die Zutaten
//hier explizit abgerufen werden solange die KuchenNMBean noch nicht
//detached ist. Später würde ansonsten ein Zugriff auf die Zutatenliste eine
//Exception auslösen.
Collection<ZutatNMBean> collZutaten = kuchen.getZutaten();
//Wir müssen einen Zugriff auf die Collection selbst ausführen, Abrufen der Property alleine reicht nicht !
logger.info ("Anzahl Zutaten: " + collZutaten.size());
}
return kuchen;
}
public void deleteKuchen (Integer intKuchenId)
{
//Den Kuchen im EntityManager laden.
//Hier mit "getReference" arbeiten, damit eine böse EntityNotFoundException fliegt wenn Zutat nicht gefunden wird.
KuchenNMBean kuchen = this.entityManager.getReference(KuchenNMBean.class, intKuchenId);
//Jetzt wird es knifflig: wir müssen die Zutaten des Kuchens holen,
//und aus den Kuchen-Collections der Zutaten den Kuchen entfernen.
//Grund scheint zu sein dass der EntityManager ansonsten noch den
//Kuchen in Zutaten hält und dabei in einen Fehlerzustand läuft.
Collection listeZutaten = kuchen.getZutaten();
Iterator iteratorZutat = listeZutaten.iterator();
while (iteratorZutat.hasNext() == true)
{
//Zutat wegwerfen:
ZutatNMBean zutatVonKuchen = iteratorZutat.next();
zutatVonKuchen.getKuchen().remove(kuchen);
}
//Jetzt löschen:
this.entityManager.remove(kuchen);
}
public ZutatNMBean findZutatById(Integer int_Id)
{
ZutatNMBean zutat = this.entityManager.find(ZutatNMBean.class, int_Id);
//Wenn nichts gefunden wurde, kommt hier "null" zurück.
//Falls etwas gefunden wurde, dann die Relationship einlesen.
if (zutat != null)
{
//Die Kuchen einmal abrufen solange die Bean unter
//Container-Verwaltung ist.
Collection<KuchenNMBean> collKuchen = zutat.getKuchen();
//Wir müssen einen Zugriff auf die Collection selbst ausführen, Abrufen der Property alleine reicht nicht !
logger.info ("Anzahl Kuchen: " + collKuchen.size());
}
return zutat;
}
public void deleteZutat (Integer intZutatId)
{
//Die Zutat im EntityManager laden.
//Hier mit "getReference" arbeiten, damit eine böse EntityNotFoundException fliegt wenn Zutat nicht gefunden wird.
ZutatNMBean zutat = this.entityManager.getReference(ZutatNMBean.class, intZutatId );
//Jetzt wird es knifflig: wir müssen die Kuchen der Zutat holen,
//und aus den Zutat-Collections der 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.
Collection<KuchenNMBean> listeKuchen = zutat.getKuchen();
Iterator<KuchenNMBean> iteratorKuchen = listeKuchen.iterator();
while (iteratorKuchen.hasNext() == true)
{
//Zutat wegwerfen:
KuchenNMBean kuchenMitZutat = iteratorKuchen.next();
kuchenMitZutat.getZutaten().remove(zutat);
}
//Jetzt endlich dürfen wir die Zutat löschen.
this.entityManager.remove(zutat);
}
public void saveZutat (ZutatNMBean zutat)
{
this.entityManager.merge(zutat);
}
public List<ZutatNMBean> getZutaten()
{
Query query = this.entityManager.createNamedQuery("findAllZutaten");
List<ZutatNMBean> listZutaten = query.getResultList();
return listZutaten;
}
}
Wie im Kuchen-Zutat-Beispiel müssen wir hier die Zutat aufwändig löschen da kein CascadeType.REMOVE gesetzt ist.
Auch beim Löschen eines Kuchens ist das manuelle Anpassen der Zutaten-Seite nötig, da wir auch hier kein kaskadierendes
Löschen aktiviert haben.
Jetzt kommen zwei neue Methoden ins Spiel die die Zuordnung von Zutaten zu Kuchen verwalten.
Grund dieser Methoden: wenn eine Zutat nur zu der Zutatenliste eines Kuchens zugefügt wird dann passiert beim
Speichern rein garnichts (es wird kein Datensatz in der Datenbank erzeugt). Es müssen immer beide Seiten
des Mappings angepaßt werden:
- Die Zutat muss der Zutatenliste des Kuchens zugefügt werden:
kuchen.getZutaten().add(zutat);
- Der Kuchen muss der Kuchenliste der Zutat zugefügt werden:
zutat.getKuchen().add(kuchen);
Die gleiche Logik ist für das Entfernen einer Zutat aus dem Kuchen nötig !
So etwas funktioniert auch mit den detached Entities z.B. im Webclient (wahrscheinlich weil das Speichern dann
vom Kuchen über die Zutatenliste weiterkaskadiert), allerdings habe ich es zur Sicherheit in die SessionBean
verlegt, wo wir den Entity Manager zur Verfügung haben.
public void addZutatToKuchen (Integer intKuchenId, Integer intZutatId)
{
//Kuchen und Zutat laden:
//Es wird "getReference" verwendet, um eine Exception zu provozieren falls kein Datensatz gefunden wird.
KuchenNMBean kuchen = this.entityManager.getReference(KuchenNMBean.class, intKuchenId );
ZutatNMBean zutat = this.entityManager.getReference(ZutatNMBean.class, intZutatId );
//Jetzt BEIDEN Seiten des Mappings zufügen !
kuchen.getZutaten().add(zutat);
zutat.getKuchen().add(kuchen);
//Und eine Seite des Mappings speichern (hier dürfen wir "persist" nehmen, da alle beteiligten Entities
//unter Kontrolle des Persistence-Managers sind, "merge" ist nicht nötig).
this.entityManager.persist(kuchen);
}
public void removeZutatFromKuchen (Integer intKuchenId, Integer intZutatId)
{
//Kuchen und Zutat laden:
//Es wird "getReference" verwendet, um eine Exception zu provozieren falls kein Datensatz gefunden wird.
KuchenNMBean kuchen = this.entityManager.getReference(KuchenNMBean.class, intKuchenId );
ZutatNMBean zutat = this.entityManager.getReference(ZutatNMBean.class, intZutatId);
//Jetzt in BEIDEN Seiten des Mappings entfernen !
kuchen.getZutaten().remove(zutat);
zutat.getKuchen().remove(kuchen);
//Und eine Seite des Mappings speichern (hier dürfen wir "Persist" nehmen, da alle beteiligten Entities
//unter Kontrolle des Persistence-Managers sind).
this.entityManager.persist(kuchen);
}
Eine Neuerung ist, dass ich mir hier Gedanken um den Fall gemacht habe, was passiert, wenn eine Entity nicht gefunden wird.
- Im "findKuchenById" bzw. "findZutatById" gehe ich davon aus, dass hier eine ungültige ID übergeben werden kann, und gebe in diesem Fall
null
zurück. EntityManager.find(...)
ist hier die Methode unserer Wahl, da diese keine Exception wirft.
Ein Verwender muss natürlich höllisch aufpassen, dass hier kein null
, und jeweils entsprechende Prüfungen einbauen.
Das ist in den Clients dieses Beispiels allerdings noch nicht geschehen.
- Beim Löschen eines Kuchens/einer Zutat oder beim Verknüpfen von Kuchen und Zutat wäre es ein schwerer Fehler, wenn eines der beiden Objekte nicht
gefunden wird. Deshalb verwende ich hier
EntityManager.getReference(...)
, denn diese Methode wirft eine (unchecked)
javax.persistence.EntityNotFoundException
.
Die Bussiness-Methoden werden in ein local Interface "KuchenZutatNMWorkerLocal" 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 "KuchenZutatNMWeb"
wechseln und unter "J2EE Module Dependencies" das EJB-JAR wählen.
EJB-Verweise in "web.xml" festlegen:
<ejb-local-ref>
<ejb-ref-name>ejb/KuchenZutatNMWorkerLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>java.lang.Object</local-home>
<local>de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenZutatNMWorkerLocal</local>
</ejb-local-ref>
"jboss-web.xml" sollte so aussehen:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
"-//JBoss//DTD Web Application 4.2//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_4_2.dtd">
<jboss-web>
<context-root>KuchenZutatNMWeb</context-root>
<ejb-local-ref>
<ejb-ref-name>ejb/KuchenZutatNMWorkerLocal</ejb-ref-name>
<local-jndi-name>KuchenZutatNM/KuchenZutatNMWorkerBean/local</local-jndi-name>
</ejb-local-ref>
</jboss-web>
Es müssen vier JSP-Seiten "index.jsp", "KuchenEdit.jsp", "ZutatEdit.jsp" und "KuchenZutaten.jsp" zugefügt werden.
Jetzt die Anwendung nur noch deployen. Sie ist unter
http://localhost:8080/KuchenZutatNMWeb/index.jsp zu erreichen.
Blick in die Datenbank
In der Datenbank sieht das so aus:
Der Name der Mapping-Datenbank wurde erzeugt aus den Namen der beiden beteiligten Beans. Im rechten
unteren Teil des Screenshots sind die Inhalte der KuchenNMBean-Tabelle (oben) und der ZutatNMBean-Tabelle (darunter)
dargestellt.
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>
KuchenZutatNMEJB</display-name>
<enterprise-beans>
<session>
<description>
<![CDATA[Stateless Session Bean für das Arbeiten mit Kuchen und Zutaten.]]>
</description>
<display-name>KuchenZutatNMWorkerBean</display-name>
<ejb-name>KuchenZutatNMWorkerBean</ejb-name>
<local>de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenZutatNMWorkerLocal</local>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenZutatNMWorkerBean</ejb-class>
<session-type>Stateless</session-type>
<!--EntityManager-Injection -->
<persistence-context-ref>
<persistence-context-ref-name>KuchenZutatNMPersistenceUnitRef</persistence-context-ref-name>
<persistence-unit-name>kuchenZutatNMPersistenceUnit</persistence-unit-name>
<injection-target>
<injection-target-class>
de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenZutatNMWorkerBean
</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>KuchenNMBean</display-name>
<ejb-name>KuchenNMBean</ejb-name>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenNMBean</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>ZutatNMBean</display-name>
<ejb-name>ZutatNMBean</ejb-name>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchenzutatnm.ZutatNMBean</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 KuchenZutatNM-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 KuchenNMBean o</query>
</named-query>
<named-query name="findAllZutaten">
<query>select o from ZutatNMBean o</query>
</named-query>
<entity class="de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenNMBean" access="PROPERTY"
metadata-complete="true">
<table name="KUCHENNMBEAN"></table>
<attributes>
<id name="id">
<column name="ID" />
<generated-value />
</id>
<basic name="name">
<column name="NAME" />
</basic>
<many-to-many name="zutaten" mapped-by="kuchen" fetch="LAZY"
target-entity="de.fhw.swtvertiefung.knauf.kuchenzutatnm.ZutatNMBean">
<cascade>
<cascade-persist />
<cascade-merge />
<cascade-refresh />
</cascade>
</many-to-many>
</attributes>
</entity>
<entity class="de.fhw.swtvertiefung.knauf.kuchenzutatnm.ZutatNMBean" access="PROPERTY"
metadata-complete="true">
<table name="ZUTATNMBEAN"></table>
<attributes>
<id name="id">
<column name="ID" />
<generated-value />
</id>
<basic name="zutatName">
<column name="ZUTATNAME" />
</basic>
<many-to-many name="kuchen" fetch="LAZY"
target-entity="de.fhw.swtvertiefung.knauf.kuchenzutatnm.KuchenNMBean">
<cascade>
<cascade-persist />
<cascade-merge />
<cascade-refresh />
</cascade>
</many-to-many>
</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: KucheZutatNMNoAnnotation.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen KuchenZutatNM-Beispiel existieren !
Stand 20.03.2008
Historie:
12.11.2007: Aus Vorjahresbeispiel erstellt und angepaßt an WTP 2.0
26.11.2007: Bessere Fehlerbehandlung, wenn Entity nicht gefunden wird.
01.12.2007: Diverse Methoden-Kommentare angepaßt, einige "find" durch "getReference" ersetzt.
24.01.2008: Hinweis auf Platzierung der @JoinTable
-Annotation.
20.03.2008: "persistence.xml" enthielt keine Schema Location etc.