August 15th, 2006

OneToMany, ManyToOne und ManyToMany - Parent/Child und die konfigurierbaren Parameter

Posted by frank in Java, J2EE, Hibernate

Wichtig bei der Verwendung von Beziehungen unter Entitäten bei Hibernate ist die korrekte Definition wer Parent und wer Child ist. Davon abhängig ist, wie und wer die Beziehung in der Datenbank gepflegt wird und welche SQL-Statements ausgeführt werden.
Parents pflegen dabei die Beziehung in der Datenbank und Childs müssen lediglich in den Valueobjekten gepflegt werden.
Wir haben z.B. eine Klasse A, welche eine 1:m Beziehung zu B hat. Das heißt jedes B hat genau ein A, aber ein A kann bei mehreren B genutzt werden.
Dies sieht dann ungefähr so aus:

Klasse A:

@Entity
@Table(name = "a")
public class A {

    @Id
    private int id;

    @OneToMany(mappedBy=“a”)
    private Set<B> bs;

    public Set<B> getBs() {
        return bs;
    }
}

Klasse B:

@Entity
@Table(name = "b")
public class B {

    @Id
    private int id;

    @ManyToOne(optional = false)
    @JoinColumn(name = “a_id”, nullable=false)
    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

Die Beziehung wird nun in der Klasse B geführt durch die Methode setA(). Dabei sollte zur korrekten Implementierung bei dem alten A in der Liste B entfernt werden und bei dem neuen hinzugefügt.

    public void setA(A a) {
        if (this.a != null) this.a.getBs().remove(this);
        this.a = a;
        if (this.a != null) this.a.getBs().add(this);
    }

Nun kann es jedoch auch sinnvoll sein, dass die Pflege von A ausgeht und nicht von B. In diesem Fall muss das Mapping verändert werden:
in Klasse A:

    @OneToMany()
    @JoinColumn(name=“a_id”)
    private Set<B> bs;

in Klasse B:

    @ManyToOne(optional = false)
    @JoinColumn(name = “a_id”, insertable=false, updatable=false, nullable=false)
    private A a;

Entscheidend ist hierbei die Entfernung von mappedBy in A und hinzufügen von JoinColumn. Dadurch wird die Pflege bei A festgesetzt. Bei B muss die Angabe JoinColumn dann durch insertable=false, updatable=false erweitert werden und somit wird die Pflege des Feldes bei B deaktiviert.
Ein weiterer entscheidender Punkt bei Beziehungen sind die Cascading-Angaben, welche bei den @ManyToOne, @OneToMany, @ManyToMany und @OneToOne Annotations angegeben werden können. Damit ist ein implizites Speichern und Löschen von Objekten möglich. Z.B. sind Nachrichten zu einem Nutzer nicht mehr notwendig, wenn der Nutzer nicht mehr existiert. Die Nachrichten. Demzufolge würde auf Seiten des Nutzers oder äquivalent zu A die Beziehung wie folgt aussehen:

    @OneToMany(cascade = CascadeType.ALL, mappedBy = “a”)
    @Cascade( { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
    private Set<B> bs;

Diese Beziehungsdefinition bzw. Cascadingangaben sind auch bei ManyToMany-Beziehungen mit zusätzlichen Attributen zu empfehlen.
Die Angabe von den Hibernatespezifischen Cascading-Types steuert bei der Verwendung von Hibernate das implizite Löschen der Entitäten B beim entsprechenden Entfernen.

4 Responses to ' OneToMany, ManyToOne und ManyToMany - Parent/Child und die konfigurierbaren Parameter '

Subscribe to comments with RSS or TrackBack to ' OneToMany, ManyToOne und ManyToMany - Parent/Child und die konfigurierbaren Parameter '.

  1. Odi said,

    on August 16th, 2006 at 9:10 am

    Ich finde den Ausdruck “die Pflege geht von A aus” sehr unklar. Was willst du damit sagen? Ich nehme an, bei dieser “Umkehr der Pflege” bleibt das DB-Schema unverändert. Was ändert sich also? Ich verstehe nicht, warum man die Konfiguration absichtlich so “falsch” macht. Bist du dir sicher, dass das ein korrektes Mapping darstellt? Interpretiert Hibernate dieses in meine Augen falsche Mapping nicht mehr zufällig richtig?

  2. frank said,

    on August 16th, 2006 at 9:43 am

    Generell ist in alle Fällen die Datenbankstruktur gleich und es ändert sich daran nichts.
    Mit Pflege meine ich, welches Setzen einer Referenz ein Update auf der Datenbank anstösst. Bei einer bi-direktionalen Beziehung ist dies der Parent. Standardmäßig ist es hier die Klasse B, sprich beim Setzen des Feldes “a” in der Klasse “B” wird ein Update auf das Feld “a_id” in der Tabelle B gestartet. Durch das Hinzufügen einese Elementes B in die Liste “bs” von A, wird kein Update auf das Feld angestossen. Dies kann eben durch das Hinzufügen von @JoinColumn in der Klasse A umgekehrt werden. Es wird dann ein Update auf das Feld a_id beim Hinzufügen in die Collection ausgeführt und nicht beim Setzen des Feldes in der Klasse B.
    Ich hoffe damit ist die Sache für dich etwas verständlicher geworden.

  3. Odi said,

    on August 17th, 2006 at 5:58 pm

    Ok, danke das macht’s klarer. Scheint mir jetzt sinnvoll. Bei grösseren Schemas mit 260+ Tabellen verzichte aber lieber auf solche Convenience. Ich mache es lieber überall gleich, dann macht man weniger Fehler. Auch wenn das hier und da vielleicht ein paar Zeilen mehr Code gibt.

  4. David said,

    on November 15th, 2006 at 4:37 pm

    Hello.
    as I still did not solve my problem, I will try to reformulate my question I want to make request EJB3 QL with joint.
    here my tables:

    [CODE] CREATE TABLE `umlssnmi` (
    `AUIS` varchar(20) NOT NULL default ”,
    `CUI` varchar(20) NOT NULL default ”,
    `CODE` varchar(20) NOT NULL default ”,
    `TTY` varchar(5) NOT NULL default ”,
    `STR` varchar(255) NOT NULL default ”,
    `HIER` varchar(255) NOT NULL default ”,
    `PROF` tinyint(2) NOT NULL default ‘0′,
    `AXE` char(1) NOT NULL default ‘D’,
    PRIMARY KEY (`AUIS`),
    KEY `CODE` (`CODE`),
    KEY `TTY` (`TTY`),
    KEY `CUI` (`CUI`),
    KEY `AXE` (`AXE`)
    ) TYPE=MyISAM;

    CREATE TABLE `whoart` (
    `AUIW` varchar(10) NOT NULL default ”,
    `CODE` varchar(10) NOT NULL default ”,
    `STR` varchar(255) NOT NULL default ”,
    `FSTR` varchar(255) NOT NULL default ”,
    `SOC` varchar(5) NOT NULL default ”,
    `SEMTYPE` varchar(5) NOT NULL default ”,
    `SRC` enum(’WA’,'CIM’) NOT NULL default ‘WA’,
    PRIMARY KEY (`AUIW`),
    KEY `SOC` (`SOC`),
    KEY `CODE` (`CODE`)
    ) TYPE=MyISAM;
    [/CODE]

    Knowing that I have the following table which has like clès primary both clès primary of the tables the preceding one.

    [CODE] CREATE TABLE `wasnmi` (
    `AUIW` varchar(20) NOT NULL default ”,
    `AUIS` varchar(20) NOT NULL default ”,
    `MODIF` varchar(10) default NULL,
    `SRC` enum(’S',’MW’,'MS’,'MA’) NOT NULL default ‘S’,
    PRIMARY KEY (`AUIW`,`AUIS`),
    KEY `MOD` (`SRC`),
    KEY `MODIF` (`MODIF`)
    ) TYPE=MyISAM;

    [/CODE]

    knowing that cardinality between these tables and ManyToMany, my question and to know if so that I can make a join

    between my tables I must create tables intermédiates which contain that the primary keys like umlssnmi_wasnmi kind

    and whoart_wasnmi for example, and to make @JoinTable; or the table “wasnmi” will join the tables.

    I tried to implement :

    For table WHOART:

    [CODE] public class TermSource implements Serializable {
    […]
    @OneToMany (mappedBy=”auiw”)
    private Collection<TermProjPK> termProjPks;

    public Collection<TermProjPK> getTermProjPks() {
    return termProjPks;
    }

    public void setTermProjPks(Collection<TermProjPK> termProjPks) {
    this.termProjPks = termProjPks;
    }
    […]
    }
    [/CODE]For UMLSSNMI table:
    [CODE]

    public class SnomedInter implements Serializable {
    […]
    @OneToMany (mappedBy=”auis”)
    private Collection<TermProjPK> termProjPks;

    public Collection<TermProjPK> getTermProjPks() {
    return termProjPks;
    }

    public void setTermProjPks(Collection<TermProjPK> termProjPks) {
    this.termProjPks = termProjPks;
    }
    […]
    }[/CODE]

    The TermProjPK class which contains the primary keys of table WASNMI:

    [CODE] @Embeddable
    public class TermProjPK implements Serializable {
    […]
    @ManyToOne
    @JoinColumn(name = “AUIW”, nullable = false)
    private String auiw;
    private Collection<TermSource> termSources;

    public Collection<TermSource> getTermSources() {
    return termSources;
    }

    public void setTermSources(Collection<TermSource> termSources) {
    this.termSources = termSources;
    }
    @ManyToOne
    @JoinColumn(name = “AUIS”, nullable = false)
    private String auis;
    private Collection<SnomedInter> snomedInters;

    public Collection<SnomedInter> getSnomedInters() {
    return snomedInters;
    }

    public void setSnomedInters(Collection<SnomedInter> snomedInters) {
    this.snomedInters = snomedInters;
    }
    […]
    }[/CODE]

    For WASNMI table:
    [CODE] public class TermProj implements Serializable {

    @EmbeddedId
    protected TermProjPK termProjPK;

    @Column(name = “MODIF”)
    private String modif;

    @Column(name = “SRC”, nullable = false)
    private String src;
    […]
    }[/CODE]

    That does not function, because I do not know how I can reach the fields ` MODIF’ and ` SRC’ of the table ` WASNMI’ starting from TermSource object.

    Somebody already made there?. :roll: I thank you in advance

Leave a reply

:mrgreen: :neutral: :twisted: :shock: :smile: :???: :cool: :evil: :grin: :oops: :razz: :roll: :wink: :cry: :eek: :lol: :mad: :sad: