2012-06-15 12 views
9

Situazione

Ho Un'entità con DiscriminatorColumn, configurato per la tavola ereditarietà singola:DiscriminatorColumn come parte della chiave primaria/id

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

'ContainerAssignment' ha un riferimento a un'altra entità:

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

Un contenitore può avere uno ContainerAssignment di ciascun TIPO. Ciò significa che la chiave primaria della tabella ContainerAssignment è definita dallo CONTAINER_ID e dallo TYPE.

ContainerAssignment ha alcune sottoclassi, ad es.

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

Ci sarà solo una singola SomeTypeOfContainerAssignment istanza per un dato CONTAINER_ID.

Problema

Se io definisco l'APP @Id come solo il contenitore sul tavolo ContainerAssignment, posso fare entityManager.find(SomeTypeOfContainerAssignment.class, containerId), che è grande. Questo esegue qualcosa sulla falsariga di SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1 AND TYPE = 'SOME_TYPE';. Sa che ha bisogno del controllo TYPE qui, a causa dell'annotazione @DiscriminatorValue("SOME_TYPE") sull'entità.

Tuttavia, ciò significa che i riferimenti posteriori da Container a ContainerAssignment si interrompono poiché Container non è realmente la chiave primaria. Ad esempio, se Container ha un @OneToOne(mappedBy=container) private SomeTypeOfContainerAssignment assignment;, quando leggi in un contenitore, leggerà nell'assegnazione qualcosa come SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1;, senza il controllo del tipo. Questo dà tutti i compiti per un contenitore, e poi ne prende uno a caso, potenzialmente del tipo sbagliato, nel qual caso genera un'eccezione.

Se invece definisco JPA @Id di ContainerAssignment come un ID composito utilizzando container e tipo, i riferimenti alle sottoclassi di ContainerAssignment funzionano correttamente.

Tuttavia, non posso fare entityManager.find(SomeTypeOfContainerAssignment.class, containerId), perché containerId non è l'id. Devo fare entityManager.find(SomeTypeOfContainerAssignment.class, new MyPk(containerId, "SOME_TYPE")), che sembra sfidare il punto di @DiscriminatorValue("SOME_TYPE"). Potrei anche usare una singola Entità ContainerAssignment se devo specificare il tipo su find comunque.

Domanda

C'è un modo per avere i riferimenti di lavoro a sottoclassi di una singola entità ereditarietà delle tabelle in cui la chiave primaria sul tavolo è composto per la colonna discriminatore, pur essendo anche in grado di EntityManager.find semplicemente il parte (i) della chiave primaria che non sono il discriminatore?

risposta

0

Se Container ha un'OnetoOne bidirezionale con SomeTypeOfContainerAssignment, che si estende ContainerAssignment, allora il campo contenitore non deve essere definito e mappato in ContainerAssignment, ma 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; 
} 

Se tutti i tipi di assegnazioni contenitori hanno tale un'associazione OnetoOne con contenitore, è possibile definire il contenitore come

public abstract class ContainerAssignment { 
    @Id 
    private Long id; 

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

per essere onesti, non so se si è permesso di utilizzare lo stesso jo nella colonna della tabella per mappare i campi @OneToOne container di ciascuna sottoclasse.

Penso che questo sia il meglio che tu possa avere. Se si inserisce il campo Contenitore nella classe base, è necessario definire l'associazione come associazione OneToMany/ManyToOne, poiché è ciò che realmente è.

Non penso che ciò che si vuole fare sia possibile, e non vorrei fare confusione con i PK compositi, poiché sono scoraggiati per buoni motivi e un incubo da usare.

+1

Grazie per la tua risposta, ma in realtà non risolve la domanda - c'è un modo per mettere il tipo 'DiscriminatorColumn' all'interno del PK? Sono d'accordo che un PK artificiale è la scelta migliore, ma in molti sistemi non possiamo controllare lo schema e avere a che fare con PK compositi. –

+0

Notate anche che MAI usare chiavi primarie composite è un anti-pattern, secondo il libro di Bill Karwin. – atorres

0

Suppongo che la chiave primaria composita di ContainerAssignment funzioni correttamente (penso davvero che dipenda dall'implementazione JPA!), E tutto ciò che ancora ti infastidisce è la fastidiosa chiamata a entityManager.find e PK istanziazione.

La mia soluzione è definire i metodi finder indipendenti dall'API JPA. Non chiuderti all'APP. Il modo più semplice è quello di definire un finder statico nella classe del dominio (o, definire un'altra classe con i soli finder, se si desidera mantenere il dominio disgiunto fare JPA. Dig a IoC per sapere come farlo).

Alla ContainerAssignment (o il vostro Class Finder):

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())); 
} 

Al tuo codice:

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

Avviso che, per rendere la parte tipo di PK significa che si possono avere due istanze ContainerAssignment di distinta tipi con lo stesso id. Avrai bisogno di una Query per recuperare ContainerAssignment se non conosci il suo tipo. Se, tuttavia, il tuo id viene generato da una sequenza, puoi semplicemente scrivere un altro metodo finder che nasconde le chiamate interne all'entità framework, restituendo il primo risultato del set di risultati.