java - metodos - @OneToMany y claves primarias compuestas?
llave compuesta (8)
Estoy usando Hibernate con anotaciones (en primavera), y tengo un objeto que tiene una relación ordenada, muchos a uno, que es un objeto secundario que tiene una clave primaria compuesta, uno de cuyos componentes es una clave externa que regresa al Identificación del objeto principal.
La estructura se ve así:
+=============+ +================+
| ParentObj | | ObjectChild |
+-------------+ 1 0..* +----------------+
| id (pk) |-----------------| parentId |
| ... | | name |
+=============+ | pos |
| ... |
+================+
Probé una variedad de combinaciones de anotaciones, ninguna de las cuales parece funcionar. Esto es lo más cercano que he podido encontrar:
@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 esto después de un largo período de experimentación en el que la mayoría de mis otros intentos produjeron entidades que hibernan no podrían cargar por varias 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 obteniendo un error no único:
org.hibernate.NonUniqueObjectException:
a different object with the same identifier value was already associated
with the session: [org.kpruden.ObjectChild#null.attr1[0]]
Estoy pos
atributos de name
y pos
en mi propio código, pero, por supuesto, no conozco la ID principal, porque todavía no se ha guardado. ¿Alguna idea sobre cómo convencer a hibernate para completar esto?
¡Gracias!
Debe incorporar la referencia de ParentObject
solo en ChildObject.Pk
en lugar de asignar parent e parentId por separado:
(getters, setters, Hibernate atributos no relacionados con el problema y acceso de miembros palabras clave omitidas)
class ChildObject {
@Embeddable
static class Pk {
@ManyToOne...
@JoinColumn(name="parentId")
ParentObject parent;
@Column...
String name...
...
}
@EmbeddedId
Pk id;
}
En ParentObject
simplemente pones @OneToMany(mappedBy="id.parent")
y funciona.
Después de guardar el objeto principal, debe establecer explícitamente parentId en los objetos secundarios para que funcionen los insertos en los objetos secundarios.
Después de mucha experimentación y frustración, finalmente determiné que no puedo hacer exactamente lo que quiero.
Finalmente, seguí 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.
Después de pasar tres días en esto, creo que he encontrado una solución, pero para ser honesto, no me gusta y definitivamente 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, utilicé un objeto Collection pero debería ser igual o similar con List:
...
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 sugirió otra persona, primero debemos configurar manualmente el ID padre de todos los niños antes de guardar el padre (junto con todos los hijos)
El libro de Manning Java Persistence con 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; }
}
En primer lugar, en ParentObject
, "corrige" el atributo mappedBy
que debe establecerse en "parent"
. Además (pero esto es quizás un error tipográfico) agrega una anotación @Id
:
@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
, agrega un atributo de name
al 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 agregue insertable = false, updatable = false
a @JoinColumn
porque estamos repitiendo la columna parentId
en la asignación de esta entidad.
Con estos cambios, persistir y leer las entidades funciona bien para mí (probado con Derby).
Encontré esta pregunta buscando la respuesta a su problema, pero sus respuestas no resolvieron mi problema, porque estaba buscando @OneToMany
que no es tan adecuado para la estructura de la tabla que estaba buscando. @ElementCollection
es el adecuado para 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)
}
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.
Pude obtener una relación de uno a uno que comparte PK desde la tabla maestra mediante el uso de un generador "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 agregar @GenericGenerator y @GeneratedValue para resolver el problema de que Hibernate no asignó el PK recientemente adquirido del padre durante la inserción.