# [Hibernate] Mehrere Keys in einem <set>



## poffi (23. Feb 2007)

Längere Texte aller persistenten Klassen werden bei meiner Applikation in eine separate Tabelle gefüllt (Daran kann ich leider auch nichts ändern). Das Ganze wird mit einem <set> gemapt.

Als Fremdschlüssel gebe ich die ID des Objektes an, zu welchem die Texte gehören. Da ich nun aber verschiedene Klassen habe, deren Objekte die längeren Texte in diese Tabelle auslagern, brauche ich einen weiteren Key, der die Klasse unterscheidet.

Beim Speichern eines Objektes muss nun also neben der Objekt-Id auch eine Key für den Typ des Objektes (Klasse) abgelegt werden. Beim Lesen dürfen wiederum nur diese Datensätze aus der text-Tabelle gelesen werden, deren Type und Id übereinstimmen.

Wie realisiere ich das im Mapping bzw. kann ich das überhaupt im Mapping so definieren, dass ich beim Lesen und Schreiben keine weiteren Bedingungen brauche?

Bis anhin sieht es so aus, aber hier fehlt noch der zweite Schlüssel?


```
<set name="text" cascade="all" lazy="false">
<key update="true">
<column name="OBJECT_ID" sql-type="smallint"/>
</key>
<one-to-many class="***.text" column/>
</set>
```

Vielen Dank für alle Ideen und Tipps!


----------



## KSG9|sebastian (23. Feb 2007)

HAE?
Ich raff gar nichts. Aber so wie es sich anhört willst du ein m:n-Mapping ?!

Sowas zum Beispiel:


```
public class Person{
  private List<Adresse> adressen;
}
public class Adresse{
  private Lsit<Person> personen;
}
```

Eine Person hat n Adressen
Eine Adresse hat n Personen

Ist n mieses Beispiel, aber so allgemein passts.

Schau dir mal Hibernate many-to-many Mappings an.


Person.hbm.xml

```
<bag name="personen" table="personen_2_adressen" cascade="all" inverse="false">
        	<key column="personen_id"/>
        	<many-to-many column="adressen_id" class="Adresse" />
</bag>
```


Adresse.hbm.xml

```
<bag name="adressen" table="personen_2_adressen" cascade="all" inverse="true">
        	<key column="adressen_id"/>
        	<many-to-many column="personen_id" class="Person" />
</bag>
```
So würde das Mappiing für oben genannte Klassen aussehen. Natürlich noch Primärschlüssel u.s.w. dazu

Bei m:n-Beziehungen musst du über eine Zwischentabelle gehen, anders geht's bei Relationalen Datenbanken (zumindest nicht "legal");
Für die Zwischentabelle darfst du KEINE Mappingdatei erzeugen.


----------



## poffi (26. Feb 2007)

Danke, aber du hast mich leider nicht ganz richtig verstanden. Ich versuche es nochmals:

Bis anhin hatte ich nur eine Klasse, die mehrere Objekte aus der Klasse "Text" beinhaltete. Ich mapte in dieser Klasse ein „set“, das mehrere "Text"-Objekte enthielt (1:n). Das funktionierte prima!

Nun kommt aber eine weitere Klasse hinzu, die wiederum ein „set“ an "Text"-Objekten enthält (1:n). Würde ich nun im Mapping wieder das gleiche set mappen, käme es zu Konflikten:
Es kann nämlich sein, dass ein Objekt der Klasse "Haus" die selbe Id wie das Objekt der Klasse "Baum" hat. Dann gäbe es in der "Text"-Tabelle pötzlich einen oder mehrere Datensätze mit dem selben Fremdschlüssel. Lese ich nun alle Datensätze des "Haus"-Objekts aus, bekomme ich auch alle Datensätze des "Baum"-Objekts und das sollte nicht so sein.

Um dieses Problem zu verhindern, brauche ich einen weiteren "Key" in der "Text"-Tabelle, der mir Aus-kunft darüber gibt, um was für ein Objekt es sich handelt.

Klar wo mein Problem liegt?


----------



## KSG9|sebastian (26. Feb 2007)

Ok...hab ich das so richtig verstanden


```
public class X{
  private Set baum;
  private Set haus;
}
```

Wobei beide Sets Objekte vom gleichen Typ enthalten, richtig?
Warum gehst du nicht über eine zweite Tabelle? Alles andere ist doch Pfusch.


```
<bag name="haus" table="x_haus" cascade="all"> 
           <key column="x_id"/> 
    ...          
</bag> 

<bag name="baum" table="x_baum" cascade="all"> 
           <key column="x_id"/> 
    ...          
</bag>
```

Also wenn ich das richtig verstanden hab dann willst du in einem Objekt zwei Sets haben welche vom Typ her dieselben Objekte enthalten. Und das über irgendwelche weiteren Identifier zu machen ist sicher die schlechteste aller Lösungen.
Du hast ja bei jeder 1:n-Beziehung eine zusätzliche Tabelle, ganz egal ob die Objekte vom gleichen Typ sind.
Neue Beziehung = neue Tabelle !

Edit:
Poste mal bite n bisschen Code um das ganze übersichtlicher zu machen.


----------



## poffi (26. Feb 2007)

> Du hast ja bei jeder 1:n-Beziehung eine zusätzliche Tabelle, ganz egal ob die Objekte vom gleichen Typ sind.


Das ist doch nur bei m:n-Beziehungen, bei 1:n macht eine weitere Tabelle keinen Sinn?

So sieht es aus:


```
public class Sonne{} // objSonId = x

public class Baum{ // objBauId = 4
  private Set sonne;
} 

public class Haus{ // objHauId = 4
  private Set sonne;
}
```

Tabelle
id,     forgeinkey,      objkey
x ,     4,                   *1*
x ,     4,                   *2*


Eine weitere Tabelle, wäre hier natürlich eine Lösung, diese Alternative steht mir aber leider nicht zu Verfügung. Ich kann auch einfach eine weitere Spalte anlegen und das Ganze über hql erledigen, aber das ist auch nicht das, was ich wirklich will.
Wäre cool wenn es eine Lösung gäbe bei der ich mit Criterias und der Load-Methode arbeiten könnte.


----------



## KSG9|sebastian (26. Feb 2007)

Also für jede 1:n-Beziehung gibt es zwei Tabellen.


```
public class Person{
  private List adressen;
}
public class Adresse{

}
```

Dann haste eine Tabelle Person und eine Tabelle Adresse (mit nem FK auf Person).
Wenn du keine Tabelle anlegen kannst dann musst du wohl oder übel "pfuschen":


```
class Sonne{
   // Primary Key
  private Integer sonnenId;

  // Objekt Key
  private Integer objectKey;
}
```

http://www.hibernate.org/hib_docs/reference/en/html/collections.html
(unter 6.2 WHERE)

Im Mapping von Baum bzw. Baum bei dem Collection-Element (sonne)

Baum.hbm.xml

```
...
<set name="sonne" where="object_key=0">..</set>
```

Sonne.hbm.xml

```
...
<set name="sonne" where="object_key=1">..</set>
```

Das Property "objectKey" musst du halt manuell setzen, das ist aber kein Problem.
Kannst z.B. in der Klasse Sonne bzw. Baum die Set#add Methode überschreiben


```
public void add(Object o){
  ((Sonne)o).setObjectKey(0);
  set.add(o);
}
```
Und bei der anderen Klasse den Objektkey eben auf 1 setzen.
Damit brauchst du keine weiteren Eingriffe, nicht beim laden oder bei sonstigen Dingen.


----------



## poffi (26. Feb 2007)

Vielen Dank, dass sieht nach dem aus, was ich gesucht habe!
Werde mich damit beschäftigen (Kann aber nicht versprechen, dass das der letzte Post war ).


----------



## KSG9|sebastian (26. Feb 2007)

kein Problem, immer weiter


----------



## poffi (1. Mrz 2007)

> kein Problem, immer weiter icon_smile.gif


Wenn das so ist, habe ich noch eine Frage 

Die Idee mit dem where-Attribut bewährt sich.

In meinem Mappingfile habe ich in der Baum- und Haus-Klasse eine Set an Sonnen. Wenn ich nun aus meinem Mapping über ein Antscript ein DDL erzeuge, werden diese Beziehungen anhand von Forgeinkeys festgelegt. Füge ich nun aber eine Sonne ein, wird geprüft ob der Fremdschlüssel in der Tabelle Baum und Haus vorhanden ist, was keinen Sinn macht. 
Entferne ich die Beziehungen im DDL klappt alles wie es sollte. Nun lautet meine Frage: Wie kann ich im Mapping festlegen, das keine Fremdschlüssel gesetzt werden?


```
public class Sonne{}

public class Baum{
  private Set sonne;
}

public class Haus{ 
  private Set sonne;
}
```


----------



## KSG9|sebastian (1. Mrz 2007)

Zeig mal das erzeugte DDL, den Ant-Script und die Mapping-Datei


----------



## poffi (1. Mrz 2007)

Das würd ich gern, aber darf ich nicht, da es sich um firmeninterne Daten handelt. 

Gemappt werden die beiden Sets so:


```
<set name="sonnen" cascade="all" lazy="false" where="OBJ_KEY=1">
            <key update="true">
				<column name="OBJECT_ID" sql-type="smallint"/>
			</key>
            <one-to-many class="***.Sonne"/>
        </set>
```

Das erzeugte DDL enthält neben den Tabellendefinitionen folgende Fremdschlüsselvergaben:


```
alter table SONNE
    add constraint **************
    foreign key (OBJECT_ID) 
    references HAUS;

alter table SONNE
    add constraint **************
    foreign key (OBJECT_ID) 
    references BAUM;
```


----------



## KSG9|sebastian (2. Mrz 2007)

Haus.hbm.xml

```
<bag name="sonnen" where="..." cascade="all">
     	<key column="baum_id"/>
     	<many-to-one column="sonnen_id" class="Sonne"/>
</bag>
```
Baum.hbm.xml

```
<bag name="sonnen" where="..." cascade="all">
     	<key column="haus_id"/>
     	<many-to-one column="sonnen_id" class="Sonne"/>
</bag>
```

Kannst mal so versuchen...anstatt n bag eben dein set nehmen


----------

