2010-04-09 18 views
22

Estoy usando Hibernate con anotaciones (en primavera), y tengo un objeto que tiene una relación ordenada, muchos a uno que un objeto secundario que tiene una clave primaria compuesta, uno de cuyos componentes es una clave externa. a la identificación del objeto principal.@OneToMany y teclas primarias compuestas?

La estructura es como la siguiente:

+=============+     +================+ 
| ParentObj |     | ObjectChild | 
+-------------+ 1   0..* +----------------+ 
| id (pk)  |-----------------| parentId  | 
| ...   |     | name   | 
+=============+     | pos   | 
           | ...   | 
           +================+ 

He probado una variedad de combinaciones de anotaciones, ninguna de las cuales parece funcionar. Esto es lo más cerca que he sido capaz de llegar a:

@Entity 
public class ParentObject { 
    @Column(nullable=false, updatable=false) 
    @Id @GeneratedValue(generator="...") 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.ALL}) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    ... 
} 

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(nullable=false, updatable=false) 
     private String parentId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 

     @Override 
     public String toString() { 
      return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
     } 

     ... 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name="parentId") 
    private ParentObject parent; 

    ... 
} 

llegué a este después de un largo combate de la experimentación en la que la mayoría de mis otros intentos produjeron entidades que hibernación ni siquiera podía cargar por diversas razones .

ACTUALIZACIÓN: Gracias a todos por los comentarios; He hecho algun progreso. He realizado algunos ajustes y creo que está más cerca (he actualizado el código anterior). Ahora, sin embargo, el problema está en insertar. El objeto principal parece guardar bien, pero los objetos secundarios no se guardan, y lo que he podido determinar es que hibernate no está llenando la parte parentId de la clave primaria (compuesta) de los objetos hijo, entonces I ' m conseguir un error no es exclusivo:

org.hibernate.NonUniqueObjectException: 
    a different object with the same identifier value was already associated 
    with the session: [org.kpruden.ObjectChild#null.attr1[0]] 

estoy poblando las name y pos atributos en mi propio código, pero por supuesto no sé el ID de los padres, ya que no se ha guardado todavía. ¿Alguna idea sobre cómo convencer a hibernate para completar esto?

Gracias!

+2

Lo mejor de la suerte. Un proyecto en el que estaba tenía que abandonar por completo las claves compuestas a favor de las claves sintéticas debido al poco apoyo de Hibernate. –

+0

La excepción puede ser causada por el uso de List <>. Debe usar Set <> para evitar la duplicación de clave usando hashCode y es igual a la definición. Hibernate trata dos elementos "lógicamente idénticos" en una lista para que sean dos elementos, y luego provocó la excepción. – Popeye

Respuesta

2

Después de mucha experimentación y frustración, finalmente determiné que no puedo hacer exactamente lo que quiero.

En última instancia, me adelanté y le di al objeto secundario su propia clave sintética y dejé que Hibernate lo administrara. No es ideal, ya que la clave es casi tan grande como el resto de los datos, pero funciona.

+0

'@JoinColumns ({ @JoinColumn (name = "userfirstname_fk", referencedcolumnname = "Nombre"), @ JoinColumn (name = "userlastname_fk", referencedcolumnname = "Apellido") })' – ahaaman

+1

una buena decisión Tratando de hacer nada más que cosas básicas en Hibernate requiere demasiado anti-intuitiva de jugar un poco que suele hacer lo mismo -.. dar todo lo que una primario: simplifica las cosas para que Hibernate las entienda. – jasop

15

El libro de Manning Java Persistence with Hibernate tiene un ejemplo que describe cómo hacer esto en la Sección 7.2. Afortunadamente, incluso si no posee el libro, puede ver un ejemplo del código fuente descargando la versión JPA del proyecto de ejemplo Caveat Emptor (enlace directo here) y examinando las clases Category y CategorizedItem en el paquete auction.model.

También resumiré las anotaciones clave a continuación. Háganme saber si todavía es un no-go.

ParentObject:

@Entity 
public class ParentObject { 
    @Id @GeneratedValue 
    @Column(name = "parentId", nullable=false, updatable=false) 
    private Long id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    public Long getId() { return id; } 
    public List<ObjectChild> getAttrs() { return attrs; } 
} 

ChildObject:

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable=false, updatable=false) 
     private Long objectId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 
     ... 
    } 

    @EmbeddedId 
    private Pk id; 

    @ManyToOne 
    @JoinColumn(name="parentId", insertable = false, updatable = false) 
    @org.hibernate.annotations.ForeignKey(name = "FK_CHILD_OBJECT_PARENTID") 
    private ParentObject parent; 

    public Pk getId() { return id; } 
    public ParentObject getParent() { return parent; } 
} 
+0

Lamentablemente, eso no ayudó: recibo el mismo error. Los únicos cambios que veo de lo que tenía son la adición del atributo @ForeignKey y la adición de {insertable, updatable} = false en el atributo principal. ¿Hay algo más que no estoy viendo? Gracias! –

+0

Hay algunos otros cambios. (1) Intente cambiar el tipo de datos parentID a Long; (2) Creo que los captadores son necesarios; (3) prestar mucha atención a los nombres de columna para ParentObject # id, ParentObject # attrs, ChildObject.Pk # objectId y ChildObject # parent; (4) El campo id de ParentObject necesita la anotación @Id (y @GeneratedValue si no está suministrando manualmente la identificación); (5) Cambié el nombre de ChildObject # pk a ChildObject # id, aunque no creo que este cambio sea necesario. Si tiene todo esto, ¿puede proporcionar el código de lectura que está lanzando con algún contexto circundante? – RTBarnard

+0

Re (2) anterior: Getters y setters no son necesarios. Hibernate puede construir proxies para hacer lo que necesita hacer. Me parece mejor considerar esta "magia". – John

2

En primer lugar, en el ParentObject, "fijar" el atributo mappedBy que debe ser ajustado a "parent". También (pero esto es quizás un error tipográfico) añadir un @Id anotación:

@Entity 
public class ParentObject { 
    @Id 
    @GeneratedValue 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    // getters/setters 
} 

Luego, en ObjectChild, añadir un atributo name a la objectId en la clave compuesta:

@Entity 
public class ObjectChild { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable = false, updatable = false) 
     private String objectId; 

     @Column(nullable = false, updatable = false) 
     private String name; 

     @Column(nullable = false, updatable = false) 
     private int pos; 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name = "parentId", insertable = false, updatable = false) 
    private ParentObject parent; 

    // getters/setters 

} 

Y también añadir insertable = false, updatable = false al @JoinColumn porque estamos repitiendo la columna parentId en el mapeo de esta entidad.

Con estos cambios, persistir y leer las entidades funciona bien para mí (probado con Derby).

+0

Acabamos de pasar media hora tratando de resolver este problema y mi problema es que mi base de datos heredada tenía la clave externa en la tabla como "forecastId" y eso es lo que he copiado en mi anotación JPA y por razones desconocidas, primavera decidió que la columna debe ser, por tanto, "forecast_id" en la base de datos y se cayó al suelo. Debe ser un algoritmo para identificar automáticamente las columnas que fueron levemente incorrectas. He cambiado el '@Column (nombre =' para "forecastid" (todo en minúsculas) y la primavera decidí a comportarse. – Adam

0

Parece que te acercaste bastante, y estoy tratando de hacer lo mismo en mi sistema actual. Empecé con la clave sustituta, pero me gustaría eliminarla en favor de una clave primaria compuesta que consta del PK del padre y el índice de la lista.

yo era capaz de conseguir una relación uno-a-uno que comparte el PK de la tabla maestra mediante el uso de un generador de "extranjero":

@Entity 
@GenericGenerator(
    name = "Parent", 
    strategy = "foreign", 
    parameters = { @Parameter(name = "property", value = "parent") } 
) 
public class ChildObject implements Serializable { 
    @Id 
    @GeneratedValue(generator = "Parent") 
    @Column(name = "parent_id") 
    private int parentId; 

    @OneToOne(mappedBy = "childObject") 
    private ParentObject parentObject; 
    ... 
} 

Me pregunto si podría añadir la @GenericGenerator y @ GeneratedValue para resolver el problema de que Hibernate no asigna el PK recientemente adquirido del padre durante la inserción.

0

Después de guardar el objeto principal, tiene que establecer explícitamente parentId en los objetos secundarios para que funcionen las inserciones en los objetos secundarios.

1

Debe incorporar la referencia ParentObject solo en ChildObject.Pk en lugar de un mapa de los padres y parentId por separado:

(captadores, organismos, Hibernate no atributos relacionados con problemas de acceso y miembro de palabras clave omitidas)

class ChildObject { 
    @Embeddable 
    static class Pk { 
     @ManyToOne... 
     @JoinColumn(name="parentId") 
     ParentObject parent; 

     @Column... 
     String name... 
     ... 
    } 

    @EmbeddedId 
    Pk id; 
} 

En ParentObject usted acaba de poner @OneToMany(mappedBy="id.parent") y funciona.

1

Encontré esta pregunta en busca de la respuesta a su problema, pero sus respuestas no resolvieron mi problema, porque estaba buscando @OneToMany que no es tan bueno para la estructura de la tabla que estaba buscando. @ElementCollection es el ajuste correcto en mi caso. Sin embargo, creo que uno de los inconvenientes es que considera que toda la fila de relaciones es única, no solo la id de las filas.

@Entity 
public class ParentObject { 
@Column(nullable=false, updatable=false) 
@Id @GeneratedValue(generator="...") 
private String id; 

@ElementCollection 
@CollectionTable(name = "chidren", joinColumns = @JoinColumn(name = "parent_id")) 
private List<ObjectChild> attrs; 

... 
} 

@Embeddable 
public static class ObjectChild implements Serializable { 
    @Column(nullable=false, updatable=false) 
    private String parentId; 

    @Column(nullable=false, updatable=false) 
    private String name; 

    @Column(nullable=false, updatable=false) 
    private int pos; 

    @Override 
    public String toString() { 
     return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
    } 

    ... getters and setters REQUIRED (at least they were for me) 
} 
0

Después de pasar tres días en esto, yo creo que he encontrado una solución, pero para ser honesto, no me gusta y que sin duda se puede mejorar. Sin embargo, funciona y resuelve nuestro problema.

Aquí está el constructor de su entidad, pero también podría hacerlo en el método setter. Además, he utilizado un objeto de colección, pero debería ser igual o similar con la lista:

... 
public ParentObject(Collection<ObjectChild> children) { 
    Collection<ObjectChild> occ = new ArrayList<ObjectChild>(); 
    for(ObjectChild obj:children){ 
     obj.setParent(this); 
     occ.add(obj); 
    } 
    this.attrs = occ; 
} 
... 

Básicamente, como alguien sugirió, primero tenemos que configurar manualmente todos Identificación del padre de los niños antes de guardar el padre (junto con todos niños)

Cuestiones relacionadas