2011-01-21 16 views
10

Me está resultando muy difícil tratar de conseguir una relación unidireccional uno-a-uno para trabajar con JPA (Proveedor: Hibernate). En mi opinión esto no debería ser una molestia, pero aparentemente JPA/Hibernate no está de acuerdo con eso ;-)Mapeo unidireccional unificado JPA/Hibernate con clave primaria compartida

El problema es que tengo que mapear un esquema heredado que no puedo cambiar y que este esquema usa un primario compartido clave entre dos entidades que al mismo tiempo es la clave externa para una entidad.

he creado un TestCase simple:

DB se ve de la siguiente manera:

CREATE TABLE PARENT (PARENT_ID Number primary key, Message varchar2(50)); 

CREATE TABLE CHILD (CHILD_ID Number primary key, Message varchar2(50), 
CONSTRAINT FK_PARENT_ID FOREIGN KEY (CHILD_ID)REFERENCES PARENT (PARENT_ID)); 

CREATE SEQUENCE SEQ_PK_PARENT START WITH 1 INCREMENT BY 1 ORDER; 

El padre (= poseer lado de uno-a-uno) es el siguiente:

@Entity 
@Table(name = "PARENT") 
public class Parent implements java.io.Serializable {  
    private Long parentId; 
    private String message; 
    private Child child; 

    @Id 
    @Column(name = "PARENT_ID", unique = true, nullable = false, precision = 22, scale = 0) 
    @SequenceGenerator(name="pk_sequence", sequenceName="SEQ_PK_PARENT") 
    @GeneratedValue(generator="pk_sequence", strategy=GenerationType.SEQUENCE) 
    public Long getParentId() { 
     return this.parentId; 
    } 

    public void setParentId(Long parentId) { 
     this.parentId = parentId; 
    } 

    @Column(name = "MESSAGE", length = 50) 
    public String getMessage() { 
     return this.message; 
    } 

    public void setMessage(String message) { 
     this.message = message; 
    } 

    @OneToOne (cascade = CascadeType.ALL) 
    @PrimaryKeyJoinColumn(name="PARENT_ID", referencedColumnName="CHILD_ID") 
    public Child getTestOneToOneChild() { 
     return this.child; 
    } 

    public void setTestOneToOneChild(Child child) { 
     this.child = child; 
    } 
} 

El niño:

@Entity 
@Table(name = "TEST_ONE_TO_ONE_CHILD", schema = "EXTUSER") 
public class Child implements java.io.Serializable {  
    private static final long serialVersionUID = 1L; 
    private Long childId;  

    private String message; 

    public Child() { 
    } 

    public Child(String message) { 
     this.message = message; 
    } 

    @Id 
    @Column(name = "CHILD_ID")  
    public Long getChildId() { 
     return this.childId; 
    } 

    public void setChildId(Long childId) { 
     this.childId = childId; 
    } 

    @Column(name = "MESSAGE", length = 50) 
    public String getMessage() { 
     return this.message; 
    } 

    public void setMessage(String message) { 
     this.message = message; 
    } 
} 

Veo por completo el problema de que JPA no sabe cómo asignar la identificación para el niño. Sin embargo, también intenté usar el Generador de claves "extranjeras" de Hibernates sin éxito, porque es necesario que haya una referencia a los padres del niño que no es deseable. Este problema no me parece muy raro, entonces, ¿qué es lo que me falta aquí? ¿Hay alguna solución? También puedo usar extensiones de hibernación si el JPA puro no proporciona una solución.

Mis expectativas para un comportamiento correcto sería: Si trato de persistir el padre con un niño que se adjunta: Identificación

  1. obtener de secuencia, la puso sobre el padre
  2. persisten los padres
  3. Identificación conjunto de los padres sobre los niños
  4. persisten niño

Si trato que persista un "independiente" niño (p. entityManager.persist (aChild)) Esperaría una RuntimeException.

¡Cualquier ayuda es muy apreciada!

+0

No lo he intentado, pero tal vez funcione porque ha puesto anotaciones en getters, lo que debería indicarle a Hibernate que llene sus POJO usando setters. Podría tratar de establecer la identificación del niño en los métodos setParentId() y setChild() (si aún no tiene uno). –

+0

No, eso no funciona: IdentifierGenerationException: IDs de esta categoría deberán ser asignados manualmente antes de llamar a save() – Korgen

+0

¿Qué versión de Hibernate utiliza? – axtavt

Respuesta

3

debido a que uno tiene que tener una referencia de nuevo a los padres de los niños que no es deseable

Bueno, si un niño sólo puede existir si hay un padre, entonces no es una relación entre ellos. Puede que simplemente no desee expresar en OO, pero existe en el modelo relacional.

Dicho esto, yo diría que la solución natural para esto es tener un padre en el niño.

Pero si realmente no quieres hacer eso, te sugiero echar un vistazo a mapear el ID como una clase PK, y compartirlos con ambas clases, usando un @EmbeddedId. Estoy bastante seguro de que iba a resolver su problema, con una excepción:

Si trato de persistir un niño "independiente" (por ejemplo EntityManager.persist (UN NIÑO)) Yo esperaría un RuntimeException.

Si decide usar el enfoque @EmbeddedId en una clase PK, creo que tendrá que manejar el caso anterior como una "regla comercial".

+1

Partenon, gracias por tu respuesta. ¿Estás seguro de que el EmbeddedId del niño se completará después de que se haya recibido la identificación del padre de la secuencia de la base de datos? – Korgen

4

Para el esquema de db que ha descrito, puede utilizar anotación en la clase dependiente (la clase de niño) para lograr el mapeo de nuevo a los padres, así:

@Entity 
class Parent { 
    @Id 
    @Column(name = "parent_id") 
    @GeneratedValue 
    Long parent_id; 
} 

@Entity 
class Child { 
    @Id 
    @Column(name = "child_id") 
    Long child_id; 

    @MapsId 
    @OneToOne 
    @JoinColumn(name = "child_id") 
    Parent parent; 
} 

Agregar el mapeo de padres a niño utiliza la anotación @PrimaryKeyJoinColumn como se había mencionado, haciendo que el bidireccional completa asignación uno a uno tener este aspecto:

@Entity 
class Parent { 
    @Id 
    @Column(name = "parent_id") 
    @GeneratedValue 
    Long parent_id; 

    @OneToOne 
    @PrimaryKeyJoinColumn(name="parent_id", referencedColumnName="child_id") 
    public Child; 
} 

@Entity 
class Child { 
    @Id 
    @Column(name = "child_id") 
    Long child_id; 

    @MapsId 
    @OneToOne 
    @JoinColumn(name = "child_id") 
    Parent parent; 
} 

que utiliza el campo en lugar de acceso método (y eliminan cualquier cosa ajena a las relaciones), pero serían las mismas anotaciones aplicadas a sus captadores.

Véase también la última parte de la sección 2.2.3.1 here para otro ejemplo de @MapsId.

+0

OP no quiere referencia de padre en hijo. Quería una solución unidireccional uno a uno que creo que debe manejarse manualmente. Mis pensamientos aquí http://stackoverflow.com/questions/7892571/hibernate-code-exception-in-onetoone-mapping/12318513#12318513 –

+0

Ah, gritos. Esa parte de querer que sea unidireccional no se registró por alguna razón. –

+1

Este ejemplo muestra la anotación PrimaryKeyJoinColumn en el elemento principal, pero en cualquier otro lugar de la web veo que esto se usa solo en el elemento secundario. Aún desconcertante sobre eso. –

0

La solución de este problema es el uso de la anotación @PostPersist de la entidad matriz. Tienes que crear un método en la clase de entidad matriz y anotar con @PostPersist, por lo que este método será invocado después entidad matriz es persistir, y en este método que acabamos de ajustar el ID de la clase entidad secundaria. Vea el ejemplo a continuación.

@Entity 
@Table(name = "PARENT") 
public class Parent implements java.io.Serializable {  
    private Long parentId; 
    private String message; 
    private Child child; 

    @Id 
    @Column(name = "PARENT_ID", unique = true, nullable = false, precision = 22, scale = 0) 
    @SequenceGenerator(name="pk_sequence", sequenceName="SEQ_PK_PARENT") 
    @GeneratedValue(generator="pk_sequence", strategy=GenerationType.SEQUENCE) 
    public Long getParentId() { 
     return this.parentId; 
    } 

    public void setParentId(Long parentId) { 
     this.parentId = parentId; 
    } 

    @OneToOne (cascade = CascadeType.ALL) 
    @PrimaryKeyJoinColumn 
    public Child getTestOneToOneChild() { 
     return this.child; 
    } 

    public void setTestOneToOneChild(Child child) { 
     this.child = child; 
    } 


    @PostPersist 
    public void initializeCandidateDetailID() 
    { 
     System.out.println("reached here");// for debugging purpose 
     this.child.setChildId(parentId); // set child id here 
     System.out.println("reached here"+Id); // for debugging purpose 
    } 
} 
Cuestiones relacionadas