Hibernate: 2 Objekte desselben Typs auf 2 versch. Properties der Vaterklasse mappen

DarthShader

Erfahrenes Mitglied
Hallo,

ich habe eine - wahrscheinlich recht simple - Hibernate Frage. Ich möchte gerne ein und dieselbe Klasse in einer Tabelle unterbringen, jedoch zwei Instanzen davon, die durch einen ENUM unterschieden werden.

Konkret würde so eine Klasse z.B. so aussehen:

Java:
@Entity
@Table( name = "table_myclass" )
public class MyClass {
  public enum MyClassType {
    FIRST, SECOND
  }

  @Id
  @GeneratedValue( strategy = GenerationType.IDENTITY )
  private Integer id;

  @ManyToOne( optional = true )
  @JoinColumn( name = "parent_id" )
  private Parent parent;

  @Enumerated( EnumType.STRING )
  private MyClassType myClassType;
}

Das "parent" property ist die Vaterklasse. In der Datenbank gibt es da eben einen Fremdschlüssel, der von "table_myclass" (Spalte "parent_id") auf einen Eintrag in einer anderen Tabelle verweist, der die "Parent" Objekte enthält:

Java:
@Entity
@Table( name = "table_parent" )
public class Parent {

  @Id
  @GeneratedValue( strategy = GenerationType.IDENTITY )
  private Integer id;

  @OneToOne( mappedBy = "parent" )
  private MyClass myClassFirst;

  @OneToOne( mappedBy = "parent" )
  private MyClass myClassSecond;
}

Man sieht hier, dass die Klasse "Parent" zwei properties vom Typ "MyClass" hat. Diese Objekte werden durch das Enum "MyClass.MyClassType" unterschieden.

Das Persistieren eines solchen Konstrukts funktioniert, das Laden der Objekte aus der DB jedoch nicht. Denn Hibernate findet ja 2 Objekte, die eine Referenz auf das "Parent" Objekt haben und kann nicht entscheiden, welches zu laden ist.


Was ich nun suche ist ein Hibernate-Mechanismus, um zwei Objekte desselben Typs auf zwei verschiedene Properties (Parent.myClassFirst und Parent.myClassSecond) zu mappen, welche z.B. durch einen Enum und einer Identifier-Spalte in der Tabelle unterschieden werden. Die Tabelle für "MyClass" sieht ja so aus:

Code:
-- table_myclass
+-------------+
| id          |
| parent_id   |
| myClassType |
+-------------+

Die Spalte "myClassType" wird dann von Hibernate verwendet, ob das richtige property von "Parent" zu setzen. Diese Information muss ich Hibernate natürlich mitgeben, und ich stelle mir vielleicht sowas vor:

Java:
public class Parent {

  // [...]

  @OneToOne( mappedBy = "parent", 
      identifyColumn = "myClassType", identifiedBy = MyClassType.FIRST )
  private MyClass myClassFirst;

  @OneToOne( mappedBy = "parent", 
      identifyColumn = "myClassType", identifiedBy = MyClassType.FIRST )
  private MyClass myClassSecond;
}

Das obige ist natürlich nur fiktional, aber macht denke ich klar, wonach ich suche.

Ich kenne bereits zwei Lösungsmöglichkeiten:

  1. Als property von "Parent" mache ich eine Liste und speichere die beiden Objekte darin. Um das richtige zu finden, muss ich die Geschäftslogik nutzen bzw. Logik in der Domain Model Klasse.
  2. Ich könnte Ableitung verwenden, dort gibt es die Möglichkeit eine Spalte zu definieren, die das richtige Objekt identifiziert.

Beide Ansätze funktionieren zwar, finde ich aber eher unschön. Besser wäre es, wenn Hibernate da irgendeine direkte Unterstützung bietet.


Kennt jemand vielleicht solch eine Möglichkeit mit Hibernate (Annotations)?


Über Eure Hilfe würde ich mich sehr freuen


Vielen Dank!
 
Hallo Sentoo,

vielen Dank für Deine Antwort.

Leider bringe ich das gedanklich nicht so richtig zum Ergebnis. Wenn ich das aus Sicht von Hibernate mal darstelle, wird mein Problem vielleicht klar:

  1. Hibernate soll einen "Parent" mit der ID 1 laden
  2. Hibernate lädt das Objekt mit der ID 1 aus der Datenbank und sieht anhand seines Mappings und des Properties "private MyClass myClassFirst;", dass auch ein Eintrag vom Typ "MyClass" aus der Tabelle "table_myclass" geladen werden muss.
  3. Hibernate versucht nun, den Eintrag aus "table_myclass" zu laden, und zwar mit der ID 1 als Fremdschlüssel zu "Parent" (Spalte "parent_id")
  4. Das klappt aber nicht, weil er, trotz eines OneToOne Mappings mehrere Einträge mit der ID 1 als Fremdschlüssel (Spalte "parent_id") findet - nämlich genau einen, wo "MyClass.myClassType == MyClassType.FIRST" und einen, wo "MyClass.myClassType == MyClassType.SECOND" ist.
  5. Jetzt sagst Du, man solle einen composite FK verwenden, möglicherweise via "@EmbeddedId"? Aber wo ist die Information kodiert, dass das Property "Parent.myClassFirst" auf einen MyClass-Eintrag mit "MyClass.myClassType == MyClassType.FIRST" passt, und das Property "Parent.myClassSecond" auf einen MyClass-Eintrag mit "MyClass.myClassType == MyClassType.SECOND" passt?

Ich hoffe, Du verstehst mein gedankliches Problem dabei. Vielleicht hättest Du die Zeit, einmal die Annotations, wie Du es Dir vorstellst, an meine Klassen oben zu schreiben? Das würde mir sicher sehr weiter helfen.


Nochmals vielen Dank für Deine Hilfe!
 
Hallo Darth,

ich habe die Klassen gerade nachgebaut. Bei der Validierung treten (zu recht wie es scheint) Fehler auf. Ich bekomme angemerkt, dass die Beziehung one-to-one bei folgender Stelle falsch ist
Java:
  @OneToOne( mappedBy = "parent" )
  private MyClass myClassFirst;
 
  @OneToOne( mappedBy = "parent" )
  private MyClass myClassSecond;
Das stimmt auch, weil Du in MyClass ein many-to-one vermerkt hast.
Java:
  @ManyToOne( optional = true )
  @JoinColumn( name = "parent_id" )
  private Parent parent;
Das Gegenstück von ManyToOne muss auf der anderen Seite ein OneToMany sein und dann brauchst Du tatsächlich eine Collection, um die Menge der MyClass Objekte aufzunehmen.
Java:
	@OneToMany(mappedBy = "parent", targetEntity = MyClass.class)
	private Collection<MyClass> childrenFirst;

	@OneToMany(mappedBy = "parent", targetEntity = MyClass.class)
	private Collection<MyClass> childrenSecond;
oder Du musst im MyClassType die Beziehung auf OneToOne anpassen.
Java:
  @OneToOne( mappedBy = "parent" )
  private MyClass myClassFirst;
 
  @OneToOne( mappedBy = "parent" )
  private MyClass myClassSecond;
Dann laufen wir aber in das Problem, dass das SQL zu viele MyClass für ein Parent finden wird, genau. Dann brauchst Du den zusammengesetzten Schlüssel.

Aber, nein, das schreibe ich Dir nicht hin, dazu gibt es zig Tutorials im Internet :)

z.B.
http://download.oracle.com/javaee/5/tutorial/doc/bnbrt.html#bnbsb
http://www.coderanch.com/t/486475/ORM/java/JPA-composite-primary-key-classes

Möchtest Du das lernen oder schnell eine Aufgabe erledigt bekommen? :)
 
Hallo Sentoo,

ich habe die Klassen gerade nachgebaut. Bei der Validierung treten (zu recht wie es scheint) Fehler auf. Ich bekomme angemerkt, dass die Beziehung one-to-one bei folgender Stelle falsch ist

Das war mein Fehler. Ich habe, um meinen Code auf die eigentliche Problematik zu vereinfachen, das Beispiel mit dem "Parent" und "MyClass" erstellt und dabei sollte das "@ManyToOne" tatsächlich ein "@OneToOne" sein. Es tut mir leid, dass dies die Klärung des Themas jetzt nicht gerade vereinfacht hat :)


Aber, nein, das schreibe ich Dir nicht hin, dazu gibt es zig Tutorials im Internet
[...]
Möchtest Du das lernen oder schnell eine Aufgabe erledigt bekommen?

Ehrlich gesagt, letzteres ;) (in dieser speziellen Situation). Also offen gesagt, natürlich will man dazu lernen, es geht hier jedoch nicht um irgendeine "Hausaufgabe" oder Ähnliches. Ich denke, ich habe auch kein Problem mit einem zusammengesetzten Primärschlüssel, dafür gibt es - wie Du schon selbst sagst - etliche Beispiele.

Allerdings, und das habe ich mit den Punkten oben versucht klar zu machen, habe ich z.Z. noch ein Verständnisproblem, wenn man solch einen Schlüssel als _Fremdschlüssel_ einsetzt. Möglicherweise ist es ganz einfach, dennoch stehe ich da auf dem Schlauch.

Vielleicht nochmal anders ausgedrückt: ein einfacher Fremdschlüssel (der ja in meinem Fall nicht ausreicht) ist die ID des Parents. Diese ID ist beim Laden bekannt, denn ich will ja das Parent mit genau dieser ID laden. Der andere Wert für den Typ von MyClass ist aber nicht bekannt, weil ich ihn ja nirgendwo angebe (also das Enum "FIRST" oder "SECOND"); d.h. es steht nirgends, dass das Property "Parent.myClassFirst" auf einen MyClass-Eintrag mit "MyClass.myClassType == MyClassType.FIRST" passt.

Woher nimmt Hibernate dann diese Information?
 
Hallo Sentoo,

darf ich das Thema nochmal aufgreifen - leider weiß ich immer noch nicht, wie die richtigen Annotations aussehen müssen. Könntest Du mir dies nicht doch einmal "vormachen"? Das würde mir wirklich sehr helfen!
 

Neue Beiträge

Zurück