2009-07-13 8 views
8

Sto riscontrando un problema nel mappare un attributo incorporato di una classe. Ho creato alcune classi che sono simili a ciò che sto cercando di fare per illustrare. Fondamentalmente, ho una gerarchia di classi @Embeddable che utilizza l'ereditarietà. La classe di livello superiore "Numero parte" ha un solo attributo e le classi estendenti non aggiungono attributi alla classe "Numero parte", aggiungono solo alcune convalida/logica.JPA/Hibernate - Incorporamento di un attributo

Ecco cosa intendo:

PARTE

@Entity 
@Table(name="PART") 
public class Part { 
    private Integer id; 
    private String name; 
    private PartNumber partNumber; 

    @Id 
    @GeneratedValue(strategy=GenerationType.SEQUENCE) 
    public Integer getId() { 
     return id; 
    } 
    public void setId(Integer id) { 
     this.id = id; 
    } 

    @Column(name="PART_NAME") 
    public String getName() { 
     return name; 
    } 
    public void setName(String name) { 
     this.name = name; 
    } 

    @Embedded 
    public PartNumber getPartNumber() { 
     return partNumber; 
    } 
    public void setPartNumber(PartNumber partNumber) { 
     this.partNumber = partNumber; 
    } 

} 

PARTNUMBER

@Embeddable 
public abstract class PartNumber { 

    protected String partNumber; 
    private String generalPartNumber; 
    private String specificPartNumber; 

    private PartNumber() { 

    } 

    public PartNumber(String partNumber) { 
     this.partNumber = partNumber; 

    } 

    @Column(name = "PART_NUMBER") 
    public String getPartNumber() { 
     return partNumber; 
    } 

    public void setPartNumber(String partNumber) { 
     this.partNumber = partNumber; 
    } 

    /** 
    * @param partNumber 
    * @return 
    */ 
    public boolean validate(String partNumber) { 
     // do some validation 
     return true; 
    } 

    /** 
    * Returns the first half of the Part Number 
    * 
    * @return generalPartNumber 
    */ 
    @Transient 
    public String getGeneralPartNumber() { 
     return generalPartNumber; 

    } 

    /** 
    * Returns the last half of the Part Number 
    * which is specific to each Car Brand 
    * 
    * @return specificPartNumber 
    */ 
    @Transient 
    public String getSpecificPartNumber() { 
     return specificPartNumber; 

    } 

} 

FORD PARTNUMBER

public class FordPartNumber extends PartNumber { 

    /** 
    * Ford Part Number is formatted as 1234-#1234 
    * 
    * @param partNumber 
    */ 
    public FordPartNumber(String partNumber) { 
     super(partNumber); 
     validate(partNumber); 
    } 

    /* 
    * (non-Javadoc) 
    * 
    * @see com.test.PartNumber#validate(java.lang.String) 
    */ 
    @Override 
    public boolean validate(String partNumber) { 
     // do some validation 
     return true; 
    } 

    /* 
    * (non-Javadoc) 
    * 
    * @see com.test.PartNumber#getGeneralPartNumber() 
    */ 
    @Override 
    public String getGeneralPartNumber() { 
     return partNumber; 

    } 

    /* 
    * (non-Javadoc) 
    * 
    * @see com.test.PartNumber#getSpecificPartNumber() 
    */ 
    @Override 
    public String getSpecificPartNumber() { 
     return partNumber; 

    } 

} 

CHEVY PARTNUMBER

public class ChevyPartNumber extends PartNumber { 

    /** 
    * Chevy Part Number is formatted as 1234-$1234 
    * 
    * @param partNumber 
    */ 
    public ChevyPartNumber(String partNumber) { 
     super(partNumber); 
     validate(partNumber); 
    } 

    /* 
    * (non-Javadoc) 
    * 
    * @see com.test.PartNumber#validate(java.lang.String) 
    */ 
    @Override 
    public boolean validate(String partNumber) { 
     // do some validation 
     return true; 
    } 

    /* 
    * (non-Javadoc) 
    * 
    * @see com.test.PartNumber#getGeneralPartNumber() 
    */ 
    @Override 
    public String getGeneralPartNumber() { 
     return partNumber; 

    } 

    /* 
    * (non-Javadoc) 
    * 
    * @see com.test.PartNumber#getSpecificPartNumber() 
    */ 
    @Override 
    public String getSpecificPartNumber() { 
     return partNumber; 

    } 
} 

Naturalmente questo non funziona, perché Hibernate ignora la Gerarchia di ereditarietà e non piace il fatto che PartNumber è astratta. C'è un modo per farlo usando JPA o Hibernate Annotations? Ho provato a utilizzare l'annotazione @Interitance JPA.

Non è possibile effettuare il refactoring della parte "PartNumber" della gerarchia perché lo sviluppatore originale vuole essere in grado di estendere PartNumber con N molte classi XXXXPartNumber.

Qualcuno sa se qualcosa del genere farà parte di JPA 2.0 o di una nuova versione di Hibernate?

risposta

15

Component (es. @Embeddable) l'ereditarietà non è supportata e molto probabilmente non lo sarà mai. C'è una buona ragione per questo: l'identificatore di entità gioca un ruolo fondamentale in tutte le strategie di ereditarietà supportate da Hibernate e i componenti non hanno identificatori (mappati).

sono disponibili tre opzioni:

A) Mappa ParteNumero (e tutti i suoi discendenti) come entità. PartNumber può rimanere astratto:

@Entity 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name="part_type", discriminatorType=DiscriminatorType.STRING) 
public abstract class PartNumber { 
... 
} 

@Entity 
@DiscriminatorValue("Ford") 
public class FordPartNumber extends PartNumber { 
... 
} 

B) In base a tuo esempio sembra che tutti i discendenti PARTNUMBER differiscono nel comportamento solo (che non introducono alcuna nuova proprietà da memorizzare). In questo caso, è possibile mappare le proprietà PartNumber più il proprio valore discriminatore (in modo da sapere quale classe di istanziare) come proprietà privata @Embedded e avere accessors get/setPartNumber() nelle sottoclassi appropriate di marshall/unmarshall della classe Part. Puoi persino scrivere il tuo tipo di Hibernate personalizzato per farlo (è piuttosto semplice).

C) Se i discendenti PartNumber DO differiscono nelle proprietà che devono essere memorizzate e mapparle come entità è inaccettabile per qualsiasi motivo, è possibile utilizzare marshall/unmarshall su string (come XML o qualsiasi altra cosa che si adatta alla fattura) e immagazzina quello. Sto usando XStream per questo scopo esatto e ho scritto un semplice tipo di Hibernate per utilizzarlo. La mappatura Parte sarebbe simile

@Type(type="xmlBean") 
public PartNumber getPartNumber() { 
    return partNumber; 
} 
public void setPartNumber(PartNumber partNumber) { 
    this.partNumber = partNumber; 
} 

e discendenti PARTNUMBER non dovrà essere mappato a tutti. Il rovescio della medaglia, naturalmente, è che trattare con XML nel database è un po 'più di una seccatura, quindi potrebbe non essere l'approccio ideale per qualcosa su cui potreste avere bisogno di riferire. OTOH, sto usando questo per memorizzare le impostazioni del plugin e mi ha salvato un sacco di problemi con mapping/manutenzione del database.

+0

Per B, conosci qualche buon tutorial per il marshalling/unmarshalling di sottoclassi appropriate usando un tipo di sospensione personalizzato? – systemoutprintln

+0

Hai solo bisogno di implementare l'interfaccia UserType di Hibernate - è ragionevolmente semplice. Ecco un esempio dei documenti di Hibernate: (https://www.hibernate.org/172.html). Nel tuo caso, restituirai PartNumber dal metodo "returnedClass()" e istanzerà dinamicamente la classe discendente appropriata in "nullSafeGet()" in base a un valore memorizzato in una colonna (e ottenuto da ResultSet). – ChssPly76

1

Hmm, non sono sicuro. Hai provato a mettere @MappedSuperclasson PartNumber e @Embeddable nelle sottoclassi. Vedi anche - https://forum.hibernate.org/viewtopic.php?t=966129

Come nodo laterale: hai considerato l'utilizzo di Hibernate Validator invece del metodo di convalida nazionale?

+0

Nizza chiamata. Sono curioso di come scopre le sottoclassi, dato che la configurazione non le menziona, e la JVM non ti permette di scoprirle. – skaffman

+0

cosa intendi con "JVM non ti permette di scoprirli"? – Hardy

+0

@skaffman - Penso che sia necessario spostare il tag @Embedded nella classe genitore e utilizzare @MappedSuperclass ... possibilmente? – Petriborg

1

Quello che sembra che stiate cercando di fare è usare "Embedable Inheritance" che non credo ancora supporti l'ibernazione.

@Embeddable 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name="partType", discriminatorType=DiscriminatorType.STRING) 
public PartNumber getPartNumber() 
{ 
    return partNumber; 
} 

Dal momento che sembra che il valore discriminante tra Ford e Chevy assomiglia ad un formato. Probabilmente nasconderei la mappatura e aggiungere una funzione diversa per restituire il tipo specifico che desideri.

@Embeddable 
public PartNumber getPartNumber() 
{ 
    return partNumber; 
} 

@Transient 
public SpecificPartNumber getSpecificPartNumber() 
{ 
    return PartNumberFactory.create(getPartNumber()); 
} 

Vedere i collegamenti nei commenti ...

+0

Riferimenti: http://stackoverflow.com/questions/917974/hibernate-embeddable-inheritance http://www.coderanch.com/t/415365/Object-Relational-Mapping/java/Embeddable-inheritance – ccclark

2

Ho un problema simile nel mio schema in modo quello che ho fatto ricorso a in questo momento è come questo:

classe padre:

@Entity 
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 
@SequenceGenerator(name="SEQ", sequenceName="part_id_seq", initialValue=1, allocationSize=1) 
public abstract class BasePart { 
    @Id 
    @Column(name="part_id") 
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ") 
    protected Long partId; 

    @YourBusinessKeyAnnotation 
    @Column(name="part_number") 
    protected String partNumber 
    ... 
} 

classi figlie:

@Entity 
public class FordPart extends BasePart { 
    ... 
} 

@Entity 
public class ChevyPart extends BasePart { 
    ... 
} 

Ora potevo quindi manipolare il tasto biz che comunque avevo bisogno e questo ha funzionato bene perché ognuno dei diversi tipi di parti ha la propria tabella (che è utile per noi).

È inoltre possibile utilizzare @Embedded con @AttributeOverrides Penso di specificare i nomi di colonna in modo diverso tuttavia è necessario ... C'è un esempio dal annotation docs.

@Entity 
public class Person implements Serializable { 

    // Persistent component using defaults 
    Address homeAddress; 

    @Embedded 
    @AttributeOverrides({ 
      @AttributeOverride(name="iso2", column = @Column(name="bornIso2")), 
      @AttributeOverride(name="name", column = @Column(name="bornCountryName")) 
    }) 
    Country bornIn; 
    ... 
} 

...

@Entity 
public class Person implements Serializable { 

    // Persistent component using defaults 
    Address homeAddress; 

    @Embedded 
    @AttributeOverrides({ 
      @AttributeOverride(name="iso2", column = @Column(name="bornIso2")), 
      @AttributeOverride(name="name", column = @Column(name="bornCountryName")) 
    }) 
    Country bornIn; 
    ... 
} 

...

@Embedded 
@AttributeOverrides({ 
     @AttributeOverride(name="city", column = @Column(name="fld_city")), 
     @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2")), 
     @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName")) 
     //nationality columns in homeAddress are overridden 
}) 
Address homeAddress; 

Si può essere in grado di abusare di questo basta che non si cura ...

0

Come accennato in altre risposte, sembra che Hibernate non supporti l'ereditarietà degli oggetti incorporabili (almeno non sono riuscito a farlo funzionare). Sono venuto su con poche soluzioni temporanee (grazie @ ChssPly76 per l'ispirazione):

1) Sostituire @Embaddable da @Entity e memorizzare PartNumber gerarchia nella tabella dedicata. Naturalmente è necessario il rapporto @OneToOne da utilizzare con la classe composita (Part).

2) Introdurre la classe di entità intermedia che riflette completamente la struttura della tabella del database. E quindi mappare l'entità manualmente alle classi del modello di dominio (e viceversa). Questo approccio offre un'estrema flessibilità al prezzo del lavoro manuale per scrivere e testare il codice di mappatura.

3) Memorizzare i discendenti PartNumber in forma serializzata e fare affidamento su serializzatore che deve essere in grado di istanziare la classe corretta durante la deserializzazione.

Per il mio compito ho scelto il terzo approccio. Io uso Hibernate Types per serializzare oggetti in JSON e memorizzare il risultato nella colonna jsonb di PostgreSQL. I tipi di ibernazione usano Jackson sotto il cofano in modo che tutte le sue annotazioni (incluse quelle che controllano il comportamento polimorfico) siano a tua disposizione. Nel codice caso sarebbe come questo:

@Entity 
public class Part { 
    // ... 

    @Type(type = "com.vladmihalcea.hibernate.type.json.JsonBinaryType") 
    private PartNumber partNumber; 

} 

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") 
@JsonSubTypes(value = [ 
    JsonSubTypes.Type(value = FordPartNumber::class, name = "FordPartNumber"), 
    JsonSubTypes.Type(value = ChevyPartNumber::class, name = "ChevyPartNumber") 
]) 
public abstract class PartNumber { /* ... */ } 

public class FordPartNumber extends PartNumber { /* ... */ } 

public class ChevyPartNumber extends PartNumber { /* ... */ } 

Il risultato JSON nel database verrà sembrava

{ 
    "type": "FordPartNumber", 
    // ... other serialized properties of the FordPartNumber class 
} 
Problemi correlati