Istnieje klasa jednostek „A”. Klasa A może mieć elementy podrzędne tego samego typu „A”. Również „A” powinno trzymać swojego rodzica, jeśli jest dzieckiem.
czy to możliwe? Jeśli tak, jak mam odwzorować relacje w klasie Entity? [„A” ma kolumnę id.]
Odpowiedzi:
Tak, jest to możliwe. Jest to szczególny przypadek standardowego dwukierunkowego @ManyToOne
/ @OneToMany
relacji. Jest wyjątkowy, ponieważ jednostka na każdym końcu związku jest taka sama. Ogólny przypadek opisano szczegółowo w sekcji 2.10.2 specyfikacji JPA 2.0 .
Oto praktyczny przykład. Po pierwsze, klasa encji 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...
}
Oto przybliżona main()
metoda, która utrzymuje trzy takie jednostki:
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();
}
W takim przypadku wszystkie trzy instancje jednostki muszą zostać utrwalone przed zatwierdzeniem transakcji. Jeśli nie uda mi się utrwalić jednej z encji na wykresie relacji rodzic-dziecko, zostanie zgłoszony wyjątek commit()
. W Eclipselink jest to RollbackException
wyszczególnienie niespójności.
Takie zachowanie jest konfigurowany przez cascade
atrybutu A
„s @OneToMany
i @ManyToOne
adnotacji. Na przykład, jeśli ustawię cascade=CascadeType.ALL
obie te adnotacje, mogę bezpiecznie utrwalić jedną z jednostek i zignorować pozostałe. Powiedz, że wytrwałam parent
w transakcji. Implementacja JPA przechodzi parent
przez children
właściwość, ponieważ jest oznaczona znakiem CascadeType.ALL
. Wdrożenie JPA znajduje son
i daughter
tam. Następnie oboje dzieci utrzymują się w moim imieniu, mimo że wyraźnie o to nie prosiłem.
Jeszcze jedna uwaga. Obowiązkiem programisty jest zawsze aktualizacja obu stron relacji dwukierunkowej. Innymi słowy, ilekroć dodaję dziecko do jakiegoś rodzica, muszę odpowiednio zaktualizować jego własność rodzicielską. Aktualizowanie tylko jednej strony relacji dwukierunkowej jest błędem w JPA. Zawsze aktualizuj obie strony relacji. Jest to jednoznacznie napisane na stronie 42 specyfikacji JPA 2.0:
Należy zauważyć, że to aplikacja ponosi odpowiedzialność za utrzymanie spójności relacji w czasie wykonywania - na przykład za zapewnienie, że „jedna” i „wiele” stron relacji dwukierunkowej są ze sobą spójne, gdy aplikacja aktualizuje relację w czasie wykonywania .
Dla mnie sztuczka polegała na wykorzystaniu relacji wiele do wielu. Załóżmy, że Twój byt A jest dywizją, która może mieć podpodziały. Następnie (pomijając nieistotne szczegóły):
@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;
}
...
}
Ponieważ miałem rozbudowaną logikę biznesową dotyczącą struktury hierarchicznej, a JPA (oparty na modelu relacyjnym) jest bardzo słaba, aby ją wspierać, wprowadziłem interfejs IHierarchyElement
i odbiornik encji 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
jest to relacja. Strona 93 specyfikacji JPA 2.0, Entity Listeners i Callback Methods: „Ogólnie rzecz biorąc, metoda cyklu życia aplikacji przenośnej nie powinna wywoływać operacji EntityManager ani Query, uzyskiwać dostępu do innych instancji encji ani modyfikować relacji”. Dobrze? Daj mi znać, jeśli wyjdę.