2015-09-28 16 views
7

Ho uno strano problema in cui l'ibernazione non crea il tipo di entità previsto in una relazione molti a uno. Abbiamo i seguenti entità gerarchia sottoclasse (semplificato):Sospensione durante la creazione di un sottotipo di entità errato nella relazione

@Entity 
@Table(name = "A") 
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) 
public abstract class A { 

    @Id 
    ... 
    public Long getId() { ... } 
    ... 
} 

@Entity 
@DiscriminatorValue("1") 
public class A1 extends A { 
    ... 
} 

@Entity 
@DiscriminatorValue("2") 
public class A2 extends A { 
    ... 
} 


@Entity 
@Table(name = "B") 
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) 
public abstract class B<AClass extends A> { 

    protected AClass a; 

    @Id 
    ... 
    public Long getId() { ... } 
    ... 

    public abstract AClass getA(); 
    public void setA(AClass a) { ... } 
} 

@Entity 
@DiscriminatorValue("1") 
public class B1 extends B<A1> { 
    ... 

    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A_ID") 
    public A1 getA() { ... } 
} 

@Entity 
@DiscriminatorValue("2") 
public class B2 extends B<A2> { 
    ... 

    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A_ID") 
    public A2 getA() { ... } 
} 

In persistence.xml entrambe le entità sono dichiarate nell'ordine

A2 
A1 
B2 
B1 

Ora creare istanze di A1 e B1 nel DB:

A1 a1 = new A1(); 
entityManager.persist(a1); 
B1 b1 = new B1(); 
b1.setA(a1); 
entityManager.persist(b1); 

Posso vedere le istanze sono salvate nel DB correttamente hanno ID 1, DISCRIMINATOR è anche 1, A_ID in B è anche 1.

Quando io ora cerco di ottenere la B (in un'altra sessione Hibernate):

B b = entityManager.find(B.class, 1L); 

ottengo l'eccezione:

org.hibernate.PropertyAccessException: Exception occurred inside getter of B 
Caused by: java.lang.ClassCastException: A2 cannot be cast to A1 
at B1.getA(B1.java:61) 
... 108 more 

con il debug ho scoperto che Hibernate sta creando la giusta entità digitare B1 e crea un'entità non corretta di tipo A2 per la relazione con A. Il tipo corretto A1 viene creato se l'ordine nello persistence.xml viene modificato. Sembra che l'ibernazione non tenga conto della colonna DISCRIMINATOR di una tabella in questo caso, ma crea sempre il primo sottotipo dichiarato nella configurazione. Come si puo aggiustare? C'è qualcosa di sbagliato nelle annotazioni?

(ho avuto anche l'attuazione concreta del metodo getA() con le sue annotazioni nel supertipo B in un primo momento, ma questo porta a problemi simili.)

risposta

3

Con Hibernate 5.0.2.Final sono riuscito a far funzionare il proprio esempio utilizzando @ManyToOne(..., targetEntity = A.class). Ho anche sostituito public abstract AClass getA(); con un getter ordinario.

@Entity 
@Table(name = "B") 
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) 
public abstract class B<AClass extends A> { 
    private Long id; 
    private AClass a; 

    @Id 
    @GeneratedValue 
    public Long getId() { 
     return id; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = A.class) 
    @JoinColumn(name = "A_ID") 
    public AClass getA() { 
     return a; 
    } 

    public void setA(AClass a) { 
     this.a = a; 
    } 
} 
@Entity 
@DiscriminatorValue("1") 
public class B1 extends B<A1> { 
    // no need to override getA() 
} 
@Entity 
@DiscriminatorValue("2") 
public class B2 extends B<A2> { 
    // no need to override getA() 
} 

non ho trovato nulla di questo comportamento nella documentazione.Così ho solo le mie osservazioni:

  • Senza targetEntity = A.class Hibernate non ha nemmeno tua ricerca colonna DISCRIMINATOR del tavolo A quando avidamente recupero di righe da A con B, come se fosse già preso una decisione sulla reale tipo di A.
  • Quando ho aggiunto targetEntity = A.class, A.DISCRIMINATOR è apparso nelle query e gli oggetti sono stati creati con le sottoclassi della classe A.
+0

Grazie per la risposta. Ieri ho avuto la possibilità di testare questo e funziona nello scenario della domanda :) – Gandalf

3

Si sta utilizzando la stessa colonna di join (A_ID) sia in B1 e B2 sottoclassi.

Utilizzare una differente in ogni sottoclasse:

@Entity 
@DiscriminatorValue("1") 
public class B1 extends B<A1> { 
    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A1_ID") 
    public A1 getA() { ... } 
} 

@Entity 
@DiscriminatorValue("2") 
public class B2 extends B<A2> { 
    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A2_ID") 
    public A2 getA() { ... } 
} 

Anche se può dare un senso di riutilizzare la colonna (con colonne diverse uno sarà comunque essere null per ogni record a seconda della sottoclasse), sembra che usi Hibernate i nomi delle colonne internamente per identificare in modo univoco alcuni elementi di mapping all'interno della stessa tabella. Ecco perché probabilmente ignora la definizione del mapping molti-a-uno in B1 e utilizza quello da B2 anche per questo (perché B2 è definito prima dello B1 nello persistence.xml).

+0

Grazie Dragan per la tua risposta! :) Non ho avuto il tempo di testare se funziona perché l'altra risposta funziona e presenta alcuni vantaggi: 1. Meno codice. 2. Meno colonne DB 3. Possiamo mantenere i vincoli non nulli e le chiavi esterne sulla colonna DB. – Gandalf

Problemi correlati