2012-06-15 16 views
9

Situation

Ich habe eine Entität mit einem DiscriminatorColumn, für einzelne Tabelle Vererbung konfiguriert:DiscriminatorColumn als Teil des Primärschlüssels/id

@Entity 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name="TYPE") 
public class ContainerAssignment{ 
... 
} 

'ContainerAssignment' hat einen Verweis auf eine andere Entität:

@JoinColumn(name="CONTAINER_ID") 
private Container container; 

Ein Container kann einen ContainerAssignment von jedem TYPE haben. Dies bedeutet, dass der Primärschlüssel der Tabelle ContainerAssignment durch die CONTAINER_ID und die TYPE definiert ist.

ContainerAssignment hat einige Unterklassen, z.B.

@Entity 
@DiscriminatorValue("SOME_TYPE") 
public class SomeTypeOfContainerAssignment extends ContainerAssignment{ 
... 
} 

Es wird nur eine einzige SomeTypeOfContainerAssignment Instanz für ein bestimmtes CONTAINER_ID sein.

Problem

Wenn ich die JPA @Id als nur die Container auf der ContainerAssignment Tabelle definieren, kann ich entityManager.find(SomeTypeOfContainerAssignment.class, containerId) tun, was toll ist. Dies läuft etwas in Richtung SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1 AND TYPE = 'SOME_TYPE';. Es weiß, dass es die TYPE-Überprüfung hier wegen der @DiscriminatorValue("SOME_TYPE") Annotation auf der Entität benötigt.

Dies bedeutet jedoch, dass die Rückverweise von Container auf ContainerAssignment abbrechen, da Container nicht wirklich der Primärschlüssel ist. Zum Beispiel, wenn Container eine @OneToOne(mappedBy=container) private SomeTypeOfContainerAssignment assignment; hat, wenn Sie in einem Container lesen, wird es in der Zuordnung von etwas wie SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1;, ohne die Typprüfung lesen. Dies gibt ihm alle Zuweisungen für einen Container, und dann wählt er einen scheinbar zufällig aus, möglicherweise vom falschen Typ, in welchem ​​Fall er eine Ausnahme auslöst.

Wenn ich stattdessen die JPA @Id von ContainerAssignment als eine Composite-ID mit Container und Typ definieren, funktionieren die Verweise auf die Unterklassen von ContainerAssignment gut.

Allerdings kann ich entityManager.find(SomeTypeOfContainerAssignment.class, containerId) nicht tun, weil ContainerId nicht die ID ist. Ich muss entityManager.find(SomeTypeOfContainerAssignment.class, new MyPk(containerId, "SOME_TYPE")) tun, die den Punkt @DiscriminatorValue("SOME_TYPE") zu besiegen scheint. Ich könnte auch nur eine einzige ContainerAssignment Entity verwenden, wenn ich den Typ bei der Suche trotzdem angeben muss.

Frage

Gibt es eine Möglichkeit Arbeits Verweise auf Unterklassen von einer einzigen Tabelle Vererbung Entity zu haben, wo der Primärschlüssel auf dem Tisch auf dem Diskriminator Spalte Verbund ist, während auch EntityManager.find nur um in der Lage, das Teil (e) des Primärschlüssels, die nicht der Diskriminator sind?

Antwort

0

Wenn Container eine bidirektionale OneToOne mit SomeTypeOfContainerAssignment hat, die ContainerAssignment erstreckt, dann sollte das Containerfeld nicht in ContainerAssignment definiert und abgebildet werden, aber in SomeTypeOfContainerAssignment:

public class Container { 
    @Id 
    private Long id; 

    @OneToOne(mappedBy = "container") 
    private SomeTypeOfContainerAssignment someTypeOfContainerAssignment; 
} 

public class ContainerAssignment { 
    @Id 
    private Long id; 
} 

public class SomeTypeOfContainerAssignment extends ContainerAssignment { 
    @OneToOne 
    private Container container; 
} 

Wenn alle Arten von Container-Zuordnungen so haben eine OneToOne Verbindung mit CONTAINER, können Sie den Container als

public abstract class ContainerAssignment { 
    @Id 
    private Long id; 

    public abstract Container getContainer(); 
    public abstract void setContainer(Container container); 
} 

um ehrlich zu sein, weiß ich nicht definieren, wenn Sie die gleiche jo zu verwenden, erlaubt sind in Spalte in der Tabelle, um die @OneToOne container Felder jeder Unterklasse zuzuordnen.

Ich denke, das ist das Beste, was Sie haben können. Wenn Sie das Containerfeld in die Basisklasse einfügen, müssen Sie die Zuordnung als eine OneToMany/ManyToOne-Zuordnung definieren, da dies tatsächlich der Fall ist.

Ich glaube nicht, was Sie tun möchten, ist möglich, und ich würde nicht mit Composite-PKs, wie sie aus guten Gründen entmutigt sind, und ein Albtraum zu verwenden.

+1

Danke für Ihre Antwort, aber es löst nicht wirklich die Frage - gibt es eine Möglichkeit, die 'DiscriminatorColumn' in den PK einzubauen? Ich stimme zu, dass eine künstliche PK die beste Wahl ist, aber in vielen Systemen können wir das Schema nicht kontrollieren und müssen mit zusammengesetzten PKs umgehen. –

+0

Beachten Sie auch, dass die Verwendung von zusammengesetzten Primärschlüsseln NIE ein Anti-Muster ist, laut Bill Karwin Buch. – atorres

0

Ich gehe davon aus, dass der zusammengesetzte Primärschlüssel von ContainerAssignment funktioniert (ich glaube wirklich, dass JPA-Implementierung abhängig sein kann!), Und alles, was Sie noch stört, ist der lästige Aufruf an die EntityManager.find und PK Instanziierung.

Meine Lösung besteht darin, Finder-Methoden unabhängig von der JPA-API zu definieren. Schliesse dich nicht an JPA. Der einfachste Weg besteht darin, einfach einen statischen Finder in Ihrer Domain-Klasse zu definieren (oder eine andere Klasse nur mit Findern zu definieren, wenn Sie die Domain entkoppelt halten möchten, JPA. Dig auf IoC, um zu wissen, wie das geht).

Bei ContainerAssignment (oder Ihre Finder-Klasse):

public static <T extends ContainerAssignment> T findByPK(EntityManager manager,Class<T> type,long id) { 
    DiscriminatorValue val = type.getAnnotation(DiscriminatorValue.class); // this is not optimal...can be cached... 
    return (T) manager.find(type, new MyPk(containerId, val.getValue())); 
} 

An Code:

SomeTypeOfContainerAssignment ca = ContainerAssignment.findByPK(entityManager,SomeTypeOfContainerAssignment.class,containerId); 

Beachten Sie, dass der Teil des PK Art machen bedeutet, dass Sie zwei ContainerAssignment Instanzen verschiedener haben Typen mit der gleichen ID. Sie benötigen eine Abfrage, um ContainerAssignment abzurufen, wenn Sie ihren Typ nicht kennen. Wenn Ihre ID jedoch aus einer Sequenz generiert wird, können Sie einfach eine andere Finder-Methode schreiben, die die inneren Aufrufe an das Entity-Framework ausblendet und das erste Ergebnis des Resultsets zurückgibt.