java - onetomany - JPA: Cómo tener una relación uno a muchos del mismo tipo de Entidad
one to many jpa hibernate (2)
Hay una Clase de Entidad "A". La clase A podría tener hijos del mismo tipo "A". Además, "A" debería retener a sus padres si es un niño.
es posible? Si es así, ¿cómo debo mapear las relaciones en la clase Entity? ["A" tiene una columna de id.]
Para mí, el truco era usar una relación de muchos a muchos. Supongamos que su entidad A es una división que puede tener subdivisiones. Luego (omitiendo detalles irrelevantes):
@Entity
@Table(name = "DIVISION")
@EntityListeners( { HierarchyListener.class })
public class Division implements IHierarchyElement {
private Long id;
@Id
@Column(name = "DIV_ID")
public Long getId() {
return id;
}
...
private Division parent;
private List<Division> subDivisions = new ArrayList<Division>();
...
@ManyToOne
@JoinColumn(name = "DIV_PARENT_ID")
public Division getParent() {
return parent;
}
@ManyToMany
@JoinTable(name = "DIVISION", joinColumns = { @JoinColumn(name = "DIV_PARENT_ID") }, inverseJoinColumns = { @JoinColumn(name = "DIV_ID") })
public List<Division> getSubDivisions() {
return subDivisions;
}
...
}
Dado que tenía una lógica de negocios extensa en torno a la estructura jerárquica y JPA (basado en el modelo relacional) es muy débil para soportarlo, IHierarchyElement
interfaz IHierarchyElement
y el detector de entidades HierarchyListener
:
public interface IHierarchyElement {
public String getNodeId();
public IHierarchyElement getParent();
public Short getLevel();
public void setLevel(Short level);
public IHierarchyElement getTop();
public void setTop(IHierarchyElement top);
public String getTreePath();
public void setTreePath(String theTreePath);
}
public class HierarchyListener {
@PrePersist
@PreUpdate
public void setHierarchyAttributes(IHierarchyElement entity) {
final IHierarchyElement parent = entity.getParent();
// set level
if (parent == null) {
entity.setLevel((short) 0);
} else {
if (parent.getLevel() == null) {
throw new PersistenceException("Parent entity must have level defined");
}
if (parent.getLevel() == Short.MAX_VALUE) {
throw new PersistenceException("Maximum number of hierarchy levels reached - please restrict use of parent/level relationship for "
+ entity.getClass());
}
entity.setLevel(Short.valueOf((short) (parent.getLevel().intValue() + 1)));
}
// set top
if (parent == null) {
entity.setTop(entity);
} else {
if (parent.getTop() == null) {
throw new PersistenceException("Parent entity must have top defined");
}
entity.setTop(parent.getTop());
}
// set tree path
try {
if (parent != null) {
String parentTreePath = StringUtils.isNotBlank(parent.getTreePath()) ? parent.getTreePath() : "";
entity.setTreePath(parentTreePath + parent.getNodeId() + ".");
} else {
entity.setTreePath(null);
}
} catch (UnsupportedOperationException uoe) {
LOGGER.warn(uoe);
}
}
}
Sí, esto es posible Este es un caso especial de la relación estándar bidireccional @ManyToOne
/ @OneToMany
. Es especial porque la entidad en cada extremo de la relación es la misma. El caso general se detalla en la Sección 2.10.2 de la especificación JPA 2.0 .
Aquí hay un ejemplo trabajado. Primero, la clase de entidad A
:
@Entity
public class A implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private A parent;
@OneToMany(mappedBy="parent")
private Collection<A> children;
// Getters, Setters, serialVersionUID, etc...
}
Aquí hay un método main()
aproximado que persiste en tres de tales entidades:
public static void main(String[] args) {
EntityManager em = ... // from EntityManagerFactory, injection, etc.
em.getTransaction().begin();
A parent = new A();
A son = new A();
A daughter = new A();
son.setParent(parent);
daughter.setParent(parent);
parent.setChildren(Arrays.asList(son, daughter));
em.persist(parent);
em.persist(son);
em.persist(daughter);
em.getTransaction().commit();
}
En este caso, las tres instancias de entidad deben persistir antes de la confirmación de la transacción. Si no puedo persistir en una de las entidades en el gráfico de las relaciones padre-hijo, entonces se lanza una excepción en commit()
. En Eclipselink, esta es una RollbackException
detalla la incoherencia.
Este comportamiento se puede configurar a través del atributo cascade
en las anotaciones @OneToMany
y @ManyToOne
Por ejemplo, si configuro cascade=CascadeType.ALL
en ambas anotaciones, podría persistir con seguridad en una de las entidades e ignorar las demás. Digamos que persistí al parent
en mi transacción. La implementación de JPA atraviesa parent
propiedad CascadeType.ALL
parent
porque está marcada con CascadeType.ALL
. La implementación de JPA encuentra a son
e daughter
allí. Luego persiste a ambos niños en mi nombre, aunque no lo solicité explícitamente.
Una nota más Siempre es responsabilidad del programador actualizar ambos lados de una relación bidireccional. En otras palabras, cada vez que agrego un hijo a algún padre, debo actualizar la propiedad principal del hijo en consecuencia. Actualizar solo un lado de una relación bidireccional es un error bajo JPA. Siempre actualice ambos lados de la relación. Esto está escrito inequívocamente en la página 42 de la especificación JPA 2.0:
Tenga en cuenta que es la aplicación la responsable de mantener la coherencia de las relaciones de tiempo de ejecución, por ejemplo, para asegurar que los lados "uno" y "muchos" de una relación bidireccional sean consistentes cuando la aplicación actualice la relación en tiempo de ejecución. .