Beispiel: N:M-Relationship mit Assoziationstabelle
Inhalt:
Definition der Relation als eigene Entity
Anlegen der Session Bean "KuchenZutatAssociationTableWorkerBean"
Anlegen des Webclients
Blick in die Datenbank
Alternative: @jakarta.persistence.EmbeddedId
Ohne Annotations
Für WildFly 30 und JakartaEE 10: Dieses Beispiel erweitert das Many-To-Many-Beispiel von Kuchen und Zutat: für die Abbildung der Relation wird eine eigene
Assoziationstabelle definiert, die ein zusätzliches Feld "Menge" enthält.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet
man im Stateless-Beispiel): KuchenZutatAssociationTable.ear
Aufbau des Beispieles
a) Entity Bean für Kuchen
b) Entity Bean für Zutat
c) Entity Bean für die Verknüpfung von Kuchen und Zutat
d) Session Bean für das Arbeiten mit Zutaten und Kuchen (mit Local-Interface).
e) Webclient
Das Beispiel besteht aus einem "EAR Application Project" mit dem Namen "KuchenZutatAssociationTable", einem EJB-Projekt und einem Webprojekt.
Definition der Relation als eigene Entity
Unser Ziel ist es, statt einer ManyToMany-Assoziation eine Verknüpfungstabelle mit Feldern "KuchenID" und "ZutatID" sowie "Menge" zu erzeugen.
"KuchenID" und "ZutatID" sollen den Primärschlüssel bilden.
Eine besondere Schwierigkeit ergibt sich, weil der Primary Key aus zwei Relationsfeldern gebildet wird, nicht aus atomaren Datentypen.
Entity KuchenZutatAssociationTableBean
: ID-Felder
Die ID-Felder der Entity sehen so aus:
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity()
public class KuchenZutatAssociationTableBean implements Serializable
{
private Integer intKuchenId = null;
private Integer intZutatId = null;
@ManyToOne ()
@Id
@JoinColumn(name="KUCHENID"
public KuchenAssociationTableBean getKuchen()
{
return this.kuchen;
}
public void setKuchen (KuchenAssociationTableBean kuchen)
{
this.kuchen = kuchen;
}
@ManyToOne ()
@Id()
@JoinColumn(name="ZUTATID")
public ZutatAssociationTableBean getZutat()
{
return this.zutat;
}
public void setZutat (ZutatAssociationTableBean zutat)
{
this.zutat = zutat;
}
}
Kleine Besonderheit: Es wurden explizite Spaltennamen durch eine @JoinColumn
-Annotation deklariert. Dies hat rein kosmetische Gründe.
Ansonsten würde WildFly hier die Spaltennamen "KUCHEN_ID" und "ZUTAT_ID" generieren - also "Name der Property", gefolgt von einem Unterstrich,
und danach die Primärschlüsselspalte der referenzierten Entity, also in beiden Fällen "Id".
ID-Klasse:
Da wir einen Primary Key aus zwei Spalten haben, benötigen wir eine eigene ID-Klasse, um z.B. Instanzen der Entity über "EntityManager.getReference"
zu laden (siehe KuchenZutatAssociationTableWorkerBean.removeZutatFromKuchen(Integer, Integer)
)
Diese ist eine simple Java-Klasse, die eine Kopie der ID-Spalten der Entity enthält.
Wichtig ist allerdings, dass hier nicht die Entity Beans als Felder auftauchen, sondern die eigentlichen Primärschlüssel dieser Entities.
Hierfür ist die Regel: wenn die Entity "KuchenZutatAssociationTableBean" das ID-Feld "kuchen" (getKuchen/setKuchen) vom Typ "KuchenAssociationTableBean" hat,
das eine Relation zu einer anderen Entity ist, dann muss die korrespondierende Property in der IDClass ebenfalls "kuchen" heißen und vom Typ des Schlüssels der
Entität "KuchenAssociationTableBean" sein, also "Integer".
Hätte "KuchenAssociationTableBean" wiederum einen zusammengesetzten Schlüssel (und damit eine IDClass), dann müsste die Property "kuchen"
in "KuchenZutatPK" vom Typ dieser IDClass sein!
Siehe http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch05.html#d5e2401
(Kapitel "5.1.2.1. Composite identifier"):
You can define a composite primary key through several syntaxes:
...
map multiple properties as @Id properties and declare an external class to be the identifier type. This class, which needs to be Serializable,
is declared on the entity via the @IdClass annotation. The identifier type must contain the same properties as the identifier properties
of the entity: each property name must be the same, its type must be the same as well if the entity property is of a basic type, its type
must be the type of the primary key of the associated entity if the entity property is an association (either a @OneToOne or a @ManyToOne).
Eine neuere Variante der Doku findet sich hier - allerdings komplett überarbeitet und mit weniger detaillierten Beschreibungen:
https://docs.jboss.org/hibernate/orm/6.4/introduction/html_single/Hibernate_Introduction.html#composite-identifiers sowie
https://docs.jboss.org/hibernate/orm/6.4/userguide/html_single/Hibernate_User_Guide.html#identifiers-composite.
Hier ist der Eclipse-JPA-Validator hilfreich: er spuckt in einem solchen Fall eine (vermutlich erst einmal verwirrende) Meldung wie
"There is no primary key attribute to match the ID class attribute kuchen" aus.
import java.io.Serializable;
public class KuchenZutatPK implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer kuchen = null;
private Integer zutat = null;
public Integer getKuchen()
{
return this.kuchen;
}
public void setKuchen(Integer int_KuchenId)
{
this.kuchen = int_KuchenId;
}
public Integer getZutat()
{
return this.zutat;
}
public void setZutat(Integer int_ZutatId)
{
this.zutat = int_ZutatId;
}
@Override
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
if (!(o instanceof KuchenZutatPK))
{
return false;
}
KuchenZutatPK other = (KuchenZutatPK) o;
return true && (getKuchen() == null ? other.getKuchen() == null : getKuchen().equals(other.getKuchen()))
&& (getZutat() == null ? other.getZutat() == null : getZutat().equals(other.getZutat()));
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + (getKuchen() == null ? 0 : getKuchen().hashCode());
result = prime * result + (getZutat() == null ? 0 : getZutat().hashCode());
return result;
}
}
Der Code für "equals" und "hashCode" wurde mir von Eclipse generiert, da der JPA-Validator sich ansonsten beschwert hätte.
In der Entity KuchenZutatAssociationTableBean
wird diese ID-Klasse deklariert.
import jakarta.persistence.IdClass;
@Entity()
@IdClass(value=KuchenZutatPK.class)
public class KuchenZutatAssociationTableBean implements Serializable
{
...
KuchenAssociationTableBean
In der KuchenAssociationTableBean
sieht die Relation so aus:
@Entity()
public class KuchenAssociationTableBean implements Serializable
{
...
private Collection<KuchenZutatAssociationTableBean> collKuchenZutaten = new ArrayList<KuchenZutatAssociationTableBean>();
...
@OneToMany(mappedBy="kuchen", cascade={CascadeType.ALL}, fetch=FetchType.LAZY)
public Collection<KuchenZutatAssociationTableBean> getZutaten()
{
return this.collKuchenZutaten;
}
public void setZutaten (Collection<KuchenZutatAssociationTableBean> coll_KuchenZutaten)
{
this.collKuchenZutaten = coll_KuchenZutaten;
}
...
}
Die Entity hat eine Property "zutaten", die eine Liste von Entities KuchenZutatAssociationTableBean
ist. "Cascade" steht auf CascadeType.ALL
(also wird auch kaskadierend gelöscht, aber natürlich nur die Einträge in der Assoziationstabelle!),
"fetch" steht wie auch im n:m-Beispiel auf "LAZY". Die @OneToMany
-Relation in der Verknüpfungs-Entity
kaskadiert übrigens nicht weiter zur Zutat.
ZutatAssociationTableBean
In der ZutatAssociationTableBean
sieht die Relation so aus:
@Entity()
public class ZutatAssociationTableBean implements Serializable
{
...
private Collection<KuchenZutatAssociationTableBean> collKuchenVerknuepfungen = new ArrayList<KuchenZutatAssociationTableBean>();
...
@OneToMany(mappedBy="zutat", cascade={CascadeType.ALL}, fetch=FetchType.LAZY)
public Collection<KuchenZutatAssociationTableBean> getKuchen()
{
return this.collKuchenVerknuepfungen;
}
public void setKuchen (Collection<KuchenZutatAssociationTableBean> coll_KuchenVerknuepfungen)
{
this.collKuchenVerknuepfungen = coll_KuchenVerknuepfungen;
}
...
}
Anlegen der Session Bean "KuchenZutatAssociationTableWorkerBean"
Es wird eine SessionBean "KuchenZutatAssociationTableWorkerBean" (mit Local Interface "KuchenZutatAssociationTableWorkerLocal") zugefügt. Sie ist weitgehend identisch mit
der KuchenZutatNMWorkerBean
des n:m-Beispiels, nur bei der Verwaltung der Relation gibt es jetzt Unterschiede. Die Relation wird allerdings weitgehend weggekapselt,
so dass Erstellen oder Löschen einer Verknüpfung (bis auf eine Ausnahme) API-kompatibel zum n:m-Beispiel ist.
Zu beachten ist, dass der Use-Case "Ändern der Menge einer bestehenden Verknüpfung" nicht umgesetzt wurde, um das Beispiel einfach zu halten!
Beim Verknüpfen eines Kuchens mit einer Zutat ist die einzige API-Änderung zu vermerken: der Parameter "menge" wird zusätzlich übergeben.
Beim Erstellen einer Verknüpfung wird eine neue Entity KuchenZutatAssociationTableBean
sowie deren Primary Key KuchenZutatPK
erzeugt. Auch hier gilt
wie in allen bisherigen Beispielen, dass die Mapping-Entity auch der Zutatenliste des Kuchens und der Kuchenliste der Zutat zugefügt werden muss!
public void addZutatToKuchen (Integer intKuchenId, Integer intZutatId, String strMenge)
{
//Kuchen und Zutat laden:
//Es wird "getReference" verwendet, um eine Exception zu provozieren falls kein Datensatz gefunden wird.
KuchenAssociationTableBean kuchen = this.entityManager.getReference(KuchenAssociationTableBean.class, intKuchenId );
ZutatAssociationTableBean zutat = this.entityManager.getReference(ZutatAssociationTableBean.class, intZutatId );
//Neues Mapping erzeugen:
KuchenZutatAssociationTableBean kuchen2Zutat = new KuchenZutatAssociationTableBean();
kuchen2Zutat.setZutat(zutat);
kuchen2Zutat.setKuchen(kuchen);
kuchen2Zutat.setMenge(strMenge);
//Jetzt BEIDEN Seiten des Mappings zufügen!
//Würde man das nicht tun, gäbe es zwar keinen Fehler, aber es würde auch nix
//in die Datenbank gespeichert!
kuchen.getZutaten().add(kuchen2Zutat);
zutat.getKuchen().add(kuchen2Zutat);
//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);
}
Das Löschen ist sogar einfacher als im letzten Beispiel geworden: anhand von Kuchen- und Zutat-ID wird eine Primary-Key-Klasse erzeugt und mit dieser die
Verknüpfungs-Entity geladen (mittels "getReference", um eine Exception im nicht-gefunden-Fall zu provozieren). Anschließend wird diese Entity einfach gelöscht.
Hier ist kein beiderseitiges Update der verknüpften Relationship-Seiten nötig (vermutlich, weil keine Kaskadierungen zu Kuchen/Zutat definiert sind).
public void removeZutatFromKuchen (Integer intKuchenId, Integer intZutatId)
{
//Aus Kuchen und Zutat einen PK zusammenbasteln:
KuchenZutatPK kuchenZutatPK = new KuchenZutatPK();
kuchenZutatPK.setKuchen(intKuchenId);
kuchenZutatPK.setZutat(intZutatId);
//Mapping laden:
KuchenZutatAssociationTableBean kuchen2Zutat = this.entityManager.getReference(KuchenZutatAssociationTableBean.class, kuchenZutatPK);
//Killen:
this.entityManager.remove (kuchen2Zutat);
}
Anlegen des Webclients
Hier gibt es keine großen Unterschiede zum letzten Beispiel.
An allen Stellen, wo z.B. auf die Zutatenliste des Kuchens zugegriffen wurde, erhält man jetzt eine Liste von Verknüpfungs-Entities, diese enthalten allerdings
alle eine Property, um auf die verknüpfte Zutat zuzugreifen. Hier kommt es also nur zu kleinen Änderungen.
Einzige Besonderheit ist die Seite "KuchenZutaten.jsp", auf der beim Hinzufügen einer Zutat zum Kuchen ein weiteres Eingabefeld für die Menge eingebaut wurde.
Dieses enthält im Namen die Kuchen-ID ("menge_123"), um beim Hinzufügen mehrere Zutaten jeweils die Mengen eingegeben zu können.
Zu beachten ist, dass der Use-Case "Ändern der Menge einer bestehenden Verknüpfung" nicht umgesetzt wurde, um das Beispiel einfach zu halten!
Die Anwendung ist unter http://localhost:8080/KuchenZutatAssociationTableWeb/index.jsp zu erreichen.
Blick in die Datenbank
In der Datenbank sieht das so aus (mit rotem Rahmen markiert ist die Tabelle der Relation):
Im rechten Teil des Screenshots sind die Inhalte aller drei beteiligten Tabellen dargestellt.
Für die Tabelle "kuchenzutatassociationtablebean" sind außer dem Primary Key auch zwei Foreign Keys angezeigt.
Alternative: @jakarta.persistence.EmbeddedId
Eine Alternative zur "IDClass" wäre eine @jakarta.persistence.EmbeddedId
.
Eigentlich ist der Vorteil davon, dass die ID-Kombination nicht mehr doppelt gehalten werden muss (einmal in der Entity selbst, und außerdem
in abgewandelter Form in der IDClass), sondern nur noch in einer einzigen Klasse steckt, die wiederum direkt als Primärschlüsselfeld
verwendet wird.
Das klappt allerdings nur, wenn die ID-Spalten allesamt "einfache" Felder sind. Relationsfelder dürfen nicht in einer "EmbeddedId"-Klasse vorkommen.
Zitat aus der JPA-3.1-Spezifikation:
11.1.17. EmbeddedId Annotation
The EmbeddedId annotation is applied to a persistent field or property of an entity class or mapped
superclass to denote a composite primary key that is an embeddable class. The embeddable class must
be annotated as Embeddable. Relationship mappings defined within an embedded id class are not supported.
Hibernate würde diese Funktion sogar unterstützen, aber dann wäre das Beispiel nicht JPA-kompatibel.
Deshalb müssen wir für dieses Beispiel einen Schritt zurückgehen, so wie es ursprünglich in der Variante für JavaEE5 aussah (bevor
Relationsfelder als Schlüsselspalten zugelassen waren): in der Mapping-Klasse "KuchenZutatAssociationTableBean" gibt es eine Property
für Kuchen und Zutat, jeweils mit den Relationsfeldern, sowie eine Instanz der Primärschlüsselklasse, die wiederum KuchenId und ZutatId enthält.
Siehe auch http://stackoverflow.com/questions/10078224/foreign-key-mapping-inside-embeddable-class
- Die Primary-Key-Klasse
KuchenZutatPK
erhält die Annotation @jakarta.persistence.Embeddable
.
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
@Embeddable
public class KuchenZutatPK implements Serializable
{
private Integer zutatId = null;
private Integer kuchenId = null;
@Column(name="KUCHENID")
public Integer getKuchenId()
{
return this.intKuchenId;
}
public void setKuchenId(Integer kuchenId)
{
this.kuchenId = kuchenId;
}
@Column(name="ZUTATID")
public Integer getZutatId()
{
return this.intZutatId;
}
public void setZutatId(Integer zutatId)
{
this.zutatId = zutatId;
}
...equals, hashCode...
Außerdem müssen die @jakarta.persistence.Column
-Annotations in diese Klasse geschoben werden!
- In der Entity
KuchenZutatAssociationTableBean
gibt es eine Membervariable
vom Typ der Primary-Key-Klasse. Ihre Property wird mit der Annotation jakarta.persistence.EmbeddedId
versehen:
import jakarta.persistence.EmbeddedId;
...
private KuchenZutatPK pk;
@EmbeddedId
@SuppressWarnings("unused")
private KuchenZutatPK getPk()
{
return this.pk;
}
@SuppressWarnings("unused")
private void setPk(KuchenZutatPK pk)
{
this.pk = pk;
}
Diese Property hat nur private getter/setter. Dadurch ergibt sich der angenehme Zustand, dass kein fremder Code darauf zugreifen konnte und deshalb keine Änderungen außerhalb
der Entity Bean nötig sind (sprich: die Klasse kapselt ihre interne Implementierung des PK sauber).
Nur im Setter von Kuchen und Zutat, wo im vorherigen Beispiel direkt die ID-Membervariable gesetzt werden konnte, muss der Code angepaßt werden: eventuell wird die PK-Klasse
erzeugt, und die ID wird danach in diese geschrieben:
public void setKuchen (KuchenAssociationTableBean kuchen)
{
this.kuchen = kuchen;
//Kuchen-ID außerdem in Membervariable übernehmen.
//Eventuell wird PK-Klasse erzeugt:
if (this.pk == null)
{
this.pk = new KuchenZutatPK();
}
//Für den Fall "NULL" wird die ID auf "NULL" gesetzt.
if (kuchen != null)
{
this.pk.setKuchenId(kuchen.getId());
}
else
{
this.pk.setKuchenId(null);
}
}
public void setZutat (ZutatAssociationTableBean zutat)
{
this.zutat = zutat;
//Zutat-ID außerdem in Membervariable übernehmen.
//Eventuell wird PK-Klasse erzeugt:
if (this.pk == null)
{
this.pk = new KuchenZutatPK();
}
//Für den Fall "NULL" wird die ID auf "NULL" gesetzt.
if (zutat != null)
{
this.pk.setZutatId(zutat.getId());
}
else
{
this.pk.setZutatId(null);
}
}
Außerdem müssen hier die Relations-Annotations deklariert sein:
@ManyToOne()
@JoinColumn(name = "KUCHENID", insertable=false, updatable=false)
public KuchenAssociationTableBean getKuchen()
{
return this.kuchen;
}
@ManyToOne()
@JoinColumn(name = "ZUTATID", insertable=false, updatable=false)
public ZutatAssociationTableBean getZutat()
{
return this.zutat;
}
Besonderheit ist hier die @jakarta.persistence.JoinColumn
: Sie gibt an, welcher Name für die Fremdschlüsselfelder verwendet werden sollen.
Hier steht der gleiche Wert wie in den @jakarta.persistence.Column
-Annotations der EmbeddedId-Klasse.
Machen wir das nicht und kommt es zu unterschiedlichen Feldnamen, würde der Server im ungünstigsten Fall vier Felder nach seiner
Standard-Namenskonvention erzeugen ("KUCHEN_ID", "KUCHEN", "ZUTAT_ID" und "ZUTAT").
Die Attribute "insertable" und "updateable" müssen auf "false" gesetzt werden, da der Server sonst eine Fehlermeldung spuckt: das Feld würde
ansonsten zweimal beschrieben:
Caused by: org.hibernate.MappingException: Column 'KUCHENID' is duplicated in mapping for entity 'de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.KuchenZutatAssociationTableBean' (use '@Column(insertable=false, updatable=false)' when mapping multiple properties to the same column)
at org.hibernate@6.2.13.Final//org.hibernate.mapping.Value.checkColumnDuplication(Value.java:203)
at org.hibernate@6.2.13.Final//org.hibernate.mapping.MappingHelper.checkPropertyColumnDuplication(MappingHelper.java:249)
at org.hibernate@6.2.13.Final//org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:1071)
at org.hibernate@6.2.13.Final//org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:755)
at org.hibernate@6.2.13.Final//org.hibernate.mapping.RootClass.validate(RootClass.java:288)
at org.hibernate@6.2.13.Final//org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:497)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionFactoryImpl.(SessionFactoryImpl.java:251)
at org.hibernate@6.2.13.Final//org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:444)
at org.hibernate@6.2.13.Final//org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1458)
... 12 more
- In der WorkerBean sieht die Methode "addZutatToKuchen" noch genauso aus wie im letzten Beispiel: da mein Code den Primärschlüssel
nur mit privaten gettern und settern versehen hat, werden hier direkt die Methoden "setKuchen" und "setZutat" aufgerufen, statt die
EmbeddedId-Klasse zu erzeugen und in "KuchenZutatAssociationTableBean" zu setzen:
public void addZutatToKuchen (Integer intKuchenId, Integer intZutatId,
String strMenge)
{
KuchenAssociationTableBean kuchen = this.entityManager.getReference(KuchenAssociationTableBean.class, intKuchenId );
ZutatAssociationTableBean zutat = this.entityManager.getReference(ZutatAssociationTableBean.class, intZutatId );
KuchenZutatAssociationTableBean kuchen2Zutat = new KuchenZutatAssociationTableBean();
kuchen2Zutat.setKuchen(kuchen);
kuchen2Zutat.setZutat(zutat);
kuchen2Zutat.setMenge(strMenge);
kuchen.getZutaten().add(kuchen2Zutat);
zutat.getKuchen().add(kuchen2Zutat);
this.entityManager.persist(kuchen);
}
Die modifizierte Version des Projekts gibt es hier: KuchenZutatAssociationTableEmbeddedId.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen KuchenZutatAssociationTable-Beispiel existieren !
Ohne Annotations
In "ejb-jar.xml" gibt es 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="https://jakarta.ee/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm https://jakarta.ee/xml/ns/persistence/orm/orm_3_1.xsd"
version="3.1">
<named-query name="findAllKuchen">
<query>select o from KuchenAssociationTableBean o</query>
</named-query>
<named-query name="findAllZutaten">
<query>select o from ZutatAssociationTableBean o</query>
</named-query>
<entity class="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.KuchenAssociationTableBean" access="PROPERTY"
metadata-complete="true">
<attributes>
<id name="id">
<generated-value />
</id>
<basic name="name">
</basic>
<one-to-many name="zutaten" mapped-by="kuchen" fetch="LAZY"
target-entity="de.hsrm.jakartaee.knauf.knauf.kuchenzutatassociationtable.KuchenZutatAssociationTableBean">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
</attributes>
</entity>
<entity class="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.ZutatAssociationTableBean" access="PROPERTY"
metadata-complete="true">
<attributes>
<id name="id">
<generated-value />
</id>
<basic name="zutatName">
</basic>
<one-to-many name="kuchen" mapped-by="zutat" fetch="LAZY"
target-entity="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.KuchenZutatAssociationTableBean">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
</attributes>
</entity>
<entity class="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.KuchenZutatAssociationTableBean" access="PROPERTY"
metadata-complete="true">
<id-class class="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.KuchenZutatPK"/>
<attributes>
<basic name="menge">
</basic>
<many-to-one name="kuchen" id="true"
target-entity="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.KuchenAssociationTableBean">
<join-column name="KUCHENID"/>
</many-to-one>
<many-to-one name="zutat" id="true"
target-entity="de.hsrm.jakartaee.knauf.kuchenzutatassociationtable.ZutatAssociationTableBean">
<join-column name="ZUTATID" />
</many-to-one>
</attributes>
</entity>
</entity-mappings>
Neue Elemente in diesem Beispiel sind in der Deklaration der KuchenZutatAssociationTableBean
zu finden:
- Deklaration der
<id-class>
- Markierung der Primärschlüsselspalten durch das Attribut
id="true"
(nicht durch ein <id name="...">...</id>
-Element
wie bei einfachen Spalten)
- Deklaration der
<join-column>
Die modifizierte Version des Projekts gibt es hier: KuchenZutatAssociationTableNoAnnotation.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen KuchenZutatAssociationTable-Beispiel existieren !
Stand 04.02.2024
Historie:
04.02.2024: Aus JBoss7-Beispiel erstellt, Anpassungen an WildFly 30 und JakartaEE10