Respostas:
Sim, isso é possível. Este é um caso especial de relação @ManyToOne
/ bidirecional padrão @OneToMany
. É especial porque a entidade em cada extremidade do relacionamento é a mesma. O caso geral é detalhado na Seção 2.10.2 da especificação JPA 2.0 .
Aqui está um exemplo prático. Primeiro, a classe de entidade 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...
}
Aqui está um main()
método aproximado que persiste três dessas 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();
}
Nesse caso, todas as três instâncias de entidade devem ser persistidas antes da confirmação da transação. Se eu falhar em persistir uma das entidades no gráfico de relacionamentos pai-filho, uma exceção é lançada commit()
. No Eclipselink, isso é um RollbackException
detalhamento da inconsistência.
Este comportamento é configurável através do cascade
atributo em A
's @OneToMany
e @ManyToOne
anotações. Por exemplo, se eu definir cascade=CascadeType.ALL
em ambas as anotações, eu poderia persistir com segurança uma das entidades e ignorar as outras. Digamos que eu persistisse parent
em minha transação. A implementação de JPA atravessa parent
a children
propriedade porque está marcada com CascadeType.ALL
. A implementação JPA encontra son
e daughter
ali. Em seguida, persiste os dois filhos em meu nome, embora eu não tenha solicitado explicitamente.
Mais uma nota. É sempre responsabilidade do programador atualizar os dois lados de um relacionamento bidirecional. Em outras palavras, sempre que adiciono um filho a algum dos pais, devo atualizar a propriedade do pai da criança de acordo. Atualizar apenas um lado de um relacionamento bidirecional é um erro no JPA. Sempre atualize os dois lados do relacionamento. Isso está escrito de forma inequívoca na página 42 da especificação JPA 2.0:
Observe que é o aplicativo que tem a responsabilidade de manter a consistência dos relacionamentos de tempo de execução - por exemplo, para garantir que o "um" e os "muitos" lados de um relacionamento bidirecional sejam consistentes um com o outro quando o aplicativo atualiza o relacionamento em tempo de execução .
Para mim, o truque era usar o relacionamento muitos para muitos. Suponha que sua entidade A seja uma divisão que pode ter subdivisões. Então (pulando detalhes 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 eu tinha uma lógica de negócios extensa em torno da estrutura hierárquica e o JPA (baseado no modelo relacional) é muito fraco para suportá-lo, introduzi a interface IHierarchyElement
e o listener de entidade 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
seja um relacionamento. Página 93 da especificação JPA 2.0, Ouvintes de entidade e métodos de retorno de chamada: "Em geral, o método de ciclo de vida de um aplicativo portátil não deve invocar operações EntityManager ou Consulta, acessar outras instâncias de entidade ou modificar relacionamentos". Certo? Avise-me se estiver fora.