Respuestas:
Si, esto es posible. Este es un caso especial de la relación @ManyToOne
/ bidireccional estándar @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 main()
método aproximado que persiste en tres de esas 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 confirmar la transacción. Si no persisto una de las entidades en el gráfico de relaciones entre padres e hijos, se lanza una excepción commit()
. En Eclipselink, esto es un RollbackException
detalle de la inconsistencia.
Este comportamiento es configurable a través del cascade
atributo en A
's @OneToMany
y @ManyToOne
anotaciones. Por ejemplo, si establezco cascade=CascadeType.ALL
ambas anotaciones, podría persistir con seguridad una de las entidades e ignorar las otras. Digamos que persistí parent
en mi transacción. La implementación de JPA atraviesa parent
la children
propiedad porque está marcada con CascadeType.ALL
. La implementación de JPA encuentra son
y 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 del padre del hijo en consecuencia. Actualizar solo un lado de una relación bidireccional es un error en JPA. Siempre actualice ambos lados de la relación. Esto está escrito de forma inequívoca en la página 42 de la especificación JPA 2.0:
Tenga en cuenta que es la aplicación la que tiene la responsabilidad de mantener la coherencia de las relaciones en tiempo de ejecución, por ejemplo, para asegurarse de que los lados "uno" y "muchos" de una relación bidireccional sean coherentes entre sí cuando la aplicación actualiza la relación en tiempo de ejecución. .
Para mí, el truco consistía en utilizar la relación de varios a varios. Suponga 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;
}
...
}
Como tenía una lógica comercial extensa en torno a la estructura jerárquica y JPA (basado en el modelo relacional) es muy débil para admitirlo, presenté la interfaz IHierarchyElement
y el oyente 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);
}
}
}
Top
es una relación. Página 93 de la especificación JPA 2.0, Entity Listeners y Callback Methods: "En general, el método de ciclo de vida de una aplicación portátil no debe invocar operaciones de EntityManager o Query, acceder a otras instancias de entidad o modificar relaciones". ¿Correcto? Avísame si me voy.