2010-04-09 15 views
22

Ich verwende Hibernate mit Anmerkungen (im Frühjahr), und ich habe ein Objekt, das eine geordnete, viele-zu-eins-Beziehung hat, die ein untergeordnetes Objekt hat, das einen zusammengesetzten Primärschlüssel hat, von dem eine Komponente ein Fremdschlüssel ist auf die ID des übergeordneten Objekts.@ OneToMany und zusammengesetzte Primärschlüssel?

Die Struktur sieht wie folgt aus:

+=============+     +================+ 
| ParentObj |     | ObjectChild | 
+-------------+ 1   0..* +----------------+ 
| id (pk)  |-----------------| parentId  | 
| ...   |     | name   | 
+=============+     | pos   | 
           | ...   | 
           +================+ 

ich eine Vielzahl von Kombinationen von Annotationen versucht haben, von denen keine zu funktionieren scheint. Dies ist die nächste, die ich habe in der Lage zu kommen mit:

@Entity 
public class ParentObject { 
    @Column(nullable=false, updatable=false) 
    @Id @GeneratedValue(generator="...") 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.ALL}) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    ... 
} 

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(nullable=false, updatable=false) 
     private String parentId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 

     @Override 
     public String toString() { 
      return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
     } 

     ... 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name="parentId") 
    private ParentObject parent; 

    ... 
} 

ich dies kam nach einem langen Kampf des Experimentierens, in denen die meisten meiner anderen Versuchen ergaben Einheiten, die Hibernate nicht einmal aus verschiedenen Gründen nicht laden .

UPDATE: Vielen Dank für die Kommentare; Ich habe Fortschritte gemacht. Ich habe ein paar Verbesserungen vorgenommen und denke, dass es näher ist (ich habe den obigen Code aktualisiert). Jetzt ist das Problem jedoch beim Einfügen. Das übergeordnete Objekt scheint sich gut zu speichern, aber die untergeordneten Objekte werden nicht gespeichert. Was ich feststellen konnte, ist, dass der Ruhezustand den parentId-Teil des (zusammengesetzten) Primärschlüssels der untergeordneten Objekte nicht ausfüllt. m immer einen nicht-eindeutigen Fehler:

org.hibernate.NonUniqueObjectException: 
    a different object with the same identifier value was already associated 
    with the session: [org.kpruden.ObjectChild#null.attr1[0]] 

ich die name und pos Attribute in meinem eigenen Code bevölkern, aber natürlich weiß ich nicht, die übergeordnete ID, weil sie noch nicht gespeichert wurden. Irgendwelche Ideen, wie man überwintern kann, um das auszufüllen?

Danke!

+2

Viel Glück. Ein Projekt, auf dem ich war, musste aufgrund der schlechten Unterstützung durch Hibernate Composite-Schlüssel zugunsten synthetischer Schlüssel vollständig aufgeben. –

+0

Die Ausnahme kann durch die Verwendung von List <> verursacht werden. Sie müssen Set <> verwenden, um die Duplizierung der Schlüssel zu vermeiden, indem Sie hashCode und equals definition verwenden. Hibernate behandelt zwei "logisch identische" Elemente in einer Liste als zwei Elemente und verursachte dann die Ausnahme. – Popeye

Antwort

2

Nach vielen Experimenten und Frustrationen, stellte ich schließlich fest, dass ich nicht genau das tun kann, was ich will.

Letztendlich ging ich voran und gab dem Kindobjekt einen eigenen synthetischen Schlüssel und ließ Hibernate es verwalten. Es ist nicht ideal, da der Schlüssel fast so groß ist wie der Rest der Daten, aber es funktioniert.

+0

'@ JoinColumns ({ @ JoinColumn (name = "userfirstname_fk", referenzierteColumnName = "firstName"), @ JoinColumn (name = "userlastname_fk", referedColumnName = "lastName") }) ' – ahaaman

+1

Eine gute Entscheidung: Alles in Hibernate zu machen, erfordert nur zu viel unintelligentes Herumspielen, normalerweise mache ich dasselbe - gib ALLES ein primary - es hält die Dinge für Hibernate einfacher zu verstehen. – jasop

15

Das Manning-Buch Java Persistence with Hibernate enthält ein Beispiel, wie dies in Abschnitt 7.2 beschrieben wird. Glücklicherweise können Sie, selbst wenn Sie das Buch nicht besitzen, ein Quellcodebeispiel sehen, indem Sie die JPA-Version des Caveat Emptor Beispielprojekts (direkter Link here) herunterladen und die Klassen Category und CategorizedItem im auction.model Paket untersuchen.

Ich fasse auch die wichtigsten Anmerkungen unten zusammen. Lass es mich wissen, wenn es immer noch ein No-Go ist.

Parent:

@Entity 
public class ParentObject { 
    @Id @GeneratedValue 
    @Column(name = "parentId", nullable=false, updatable=false) 
    private Long id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    public Long getId() { return id; } 
    public List<ObjectChild> getAttrs() { return attrs; } 
} 

Childobject:

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable=false, updatable=false) 
     private Long objectId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 
     ... 
    } 

    @EmbeddedId 
    private Pk id; 

    @ManyToOne 
    @JoinColumn(name="parentId", insertable = false, updatable = false) 
    @org.hibernate.annotations.ForeignKey(name = "FK_CHILD_OBJECT_PARENTID") 
    private ParentObject parent; 

    public Pk getId() { return id; } 
    public ParentObject getParent() { return parent; } 
} 
+0

Leider hat das nicht geholfen: Ich bekomme den gleichen Fehler. Die einzigen Änderungen, die ich von dem, was ich gesehen habe, haben, sind das Hinzufügen des Attributs @ForeignKey und das Hinzufügen des {insertable, updatable} = false für das Elternattribut. Gibt es noch etwas, das ich nicht sehe? Danke! –

+0

Es gibt ein paar andere Änderungen. (1) Versuchen Sie, den Datentyp parentId in Long zu ändern. (2) Ich glaube, dass die Getter notwendig sind; (3) sehr genau auf die Spaltennamen für ParentObject # id, ParentObject # attrs, ChildObject.Pk # objectId und ChildObject # parent; (4) Das ID-Feld von ParentObject benötigt die Annotation @Id (und @GeneratedValue, wenn Sie die ID nicht manuell angeben); (5) Ich habe ChildObject # pk in ChildObject # id umbenannt, obwohl ich denke, dass diese Änderung nicht notwendig war. Wenn Sie all das haben, können Sie den Lesecode angeben, der mit dem umgebenden Kontext in Konflikt gerät? – RTBarnard

+0

Zu (2) oben: Getters und Setter sind nicht notwendig. Hibernate kann Proxies konstruieren, um das zu tun, was getan werden muss. Ich finde es am besten, diese "Magie" zu berücksichtigen. – John

2

Zum einen in der ParentObject "fix" das mappedBy Attribut, das auf "parent" gesetzt werden sollte. Auch (aber das ist vielleicht ein Tippfehler) in eine @Id Anmerkung:

@Entity 
public class ParentObject { 
    @Id 
    @GeneratedValue 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    // getters/setters 
} 

Dann in ObjectChild, fügen Sie ein name Attribut auf den objectId in dem Verbundschlüssel:

@Entity 
public class ObjectChild { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable = false, updatable = false) 
     private String objectId; 

     @Column(nullable = false, updatable = false) 
     private String name; 

     @Column(nullable = false, updatable = false) 
     private int pos; 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name = "parentId", insertable = false, updatable = false) 
    private ParentObject parent; 

    // getters/setters 

} 

UND auch hinzufügen insertable = false, updatable = false an die @JoinColumn, weil wir die parentId Spalte in der Zuordnung dieser Entität wiederholen.

Mit diesen Änderungen funktioniert persistieren und lesen der Entitäten für mich (getestet mit Derby).

+0

Ich habe gerade eine halbe Stunde damit verbracht, dieses Problem zu lösen, und mein Problem war, dass meine Legacy-Datenbank den Fremdschlüssel in der Tabelle als "ForecastId" hatte und das habe ich kopiert In meiner JPA Annotation und aus unbekannten Gründen entschied Spring, dass die Spalte daher in der Datenbank "Forecast_id" sein muss und fiel um. Muss ein Algorithmus sein, um automatisch Spalten zu identifizieren, die leicht falsch waren. Ich änderte die '@Column (Name =' zu "Forecastid" (alle Kleinbuchstaben) und Spring entschied sich zu verhalten. – Adam

0

Es scheint, dass Sie ziemlich nah dran sind, und ich versuche, das gleiche in meinem aktuellen System zu tun. Ich habe mit dem Ersatzschlüssel begonnen, möchte ihn aber zugunsten eines zusammengesetzten Primärschlüssels entfernen, der aus dem PK des Elternteils und dem Index in der Liste besteht.

Ich war in der Lage, eine Beziehung Eins-zu-eins zu bekommen, die mit einem „fremden“ Generator die PK von der Master-Tabelle teilt:

@Entity 
@GenericGenerator(
    name = "Parent", 
    strategy = "foreign", 
    parameters = { @Parameter(name = "property", value = "parent") } 
) 
public class ChildObject implements Serializable { 
    @Id 
    @GeneratedValue(generator = "Parent") 
    @Column(name = "parent_id") 
    private int parentId; 

    @OneToOne(mappedBy = "childObject") 
    private ParentObject parentObject; 
    ... 
} 

Ich frage mich, ob Sie die @GenericGenerator und könnten hinzufügen @ GeneratedValue, um das Problem zu lösen, dass Hibernate während des Einfügens nicht die neu erworbene PK des Elternteils zuweist.

0

Nach dem Speichern des Parent-Objekts müssen Sie die parentId in den Child-Objekten explizit festlegen, damit die Einfügungen für die Child-Objekte funktionieren.

1

Sie sollten die ParentObject Referenz nur in ChildObject.Pk anstatt Karte Eltern und parentId separat übernehmen:

(Getter, Setter, Attribute Hibernate nicht Problem und Mitglied Zugang Keywords ausgelassen bezogen)

class ChildObject { 
    @Embeddable 
    static class Pk { 
     @ManyToOne... 
     @JoinColumn(name="parentId") 
     ParentObject parent; 

     @Column... 
     String name... 
     ... 
    } 

    @EmbeddedId 
    Pk id; 
} 

In ParentObject setzen Sie dann einfach @OneToMany(mappedBy="id.parent") und es funktioniert.

1

Diese Frage gefunden auf der Suche nach der Antwort auf ihr Problem, aber es ist Antworten löste mein Problem nicht, weil ich nach @OneToMany suchte, die nicht so gut von einer Passform für die Tabellenstruktur ist, die ich nachging. @ElementCollection ist in meinem Fall das Richtige. Ich glaube jedoch, dass es die ganze Reihe von Beziehungen als einzigartig betrachtet, nicht nur die Zeilen-IDs.

@Entity 
public class ParentObject { 
@Column(nullable=false, updatable=false) 
@Id @GeneratedValue(generator="...") 
private String id; 

@ElementCollection 
@CollectionTable(name = "chidren", joinColumns = @JoinColumn(name = "parent_id")) 
private List<ObjectChild> attrs; 

... 
} 

@Embeddable 
public static class ObjectChild implements Serializable { 
    @Column(nullable=false, updatable=false) 
    private String parentId; 

    @Column(nullable=false, updatable=false) 
    private String name; 

    @Column(nullable=false, updatable=false) 
    private int pos; 

    @Override 
    public String toString() { 
     return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
    } 

    ... getters and setters REQUIRED (at least they were for me) 
} 
0

Nach drei Tagen auf diesen Ausgaben, ich glaube, ich habe eine Lösung gefunden, aber um ehrlich zu sein, ich mag es nicht, und es kann auf jeden Fall verbessert werden. Es funktioniert und löst unser Problem.

Hier ist Ihr Entity-Konstruktor, aber Sie könnten es auch in der Setter-Methode tun. Außerdem habe ich ein Collection-Objekt, aber es sollte mit Liste gleich oder ähnlich sein:

... 
public ParentObject(Collection<ObjectChild> children) { 
    Collection<ObjectChild> occ = new ArrayList<ObjectChild>(); 
    for(ObjectChild obj:children){ 
     obj.setParent(this); 
     occ.add(obj); 
    } 
    this.attrs = occ; 
} 
... 

Grundsätzlich wie jemand anderes vorgeschlagen, müssen wir zuerst alle Eltern id Kinder manuell einstellen, bevor die Mutter speichern (zusammen mit allen Kinder)

Verwandte Themen