JPA: Aynı Varlık türünde bire çok ilişkisine nasıl sahip olunur?


102

Varlık Sınıfı "A" var. A Sınıfının aynı "A" türünden çocukları olabilir. Ayrıca "A", eğer çocuksa ebeveyni tutmalıdır.

Mümkün mü? Öyleyse Entity sınıfındaki ilişkileri nasıl eşleştirmeliyim? ["A" bir kimlik sütununa sahiptir.]

Yanıtlar:


173

Evet, bu mümkün. Bu, standart çift yönlü @ManyToOne/ @OneToManyilişkinin özel bir durumudur . Özeldir çünkü ilişkinin her iki ucundaki varlık aynıdır. Genel durum, JPA 2.0 spesifikasyonu Bölüm 2.10.2'de detaylandırılmıştır .

İşte çalışılmış bir örnek. İlk olarak, varlık sınıfı 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...
}

İşte main()bu tür üç varlığı sürdüren kaba bir yöntem:

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();
}

Bu durumda, işlem tamamlamadan önce üç varlık eşgörünümünün tümü devam ettirilmelidir. Ebeveyn-çocuk ilişkileri grafiğindeki varlıklardan birini sürdürmezsem, bir istisna atılır commit(). Eclipselink'te bu, RollbackExceptiontutarsızlığın detaylandırılmasıdır.

Bu davranış, 's ve ek açıklamalarındaki cascadeözellik aracılığıyla yapılandırılabilir . Örneğin , bu ek açıklamaların ikisini de ayarlarsam , varlıklardan birini güvenli bir şekilde sürdürür ve diğerlerini göz ardı edebilirim. İşlemimde ısrar ettiğimi söyle . JPA uygulama traversler 'ın onunla işaretlendiği için özellik . JPA uygulaması bulur ve orada. Açıkça talep etmemiş olsam da, her iki çocuğu da benim adıma sürdürüyor.A@OneToMany@ManyToOnecascade=CascadeType.ALLparentparentchildrenCascadeType.ALLsondaughter

Bir not daha. Çift yönlü bir ilişkinin her iki tarafını da güncellemek her zaman programcının sorumluluğundadır. Diğer bir deyişle, ne zaman bir ebeveyne çocuk eklesem, çocuğun ebeveyn mülkünü buna göre güncellemeliyim. Çift yönlü bir ilişkinin yalnızca bir tarafının güncellenmesi, JPA altında bir hatadır. Daima ilişkinin her iki tarafını da güncelleyin. Bu, JPA 2.0 spesifikasyonunun 42. sayfasında açık bir şekilde yazılmıştır:

Çalışma zamanı ilişkilerinin tutarlılığını sürdürme sorumluluğunu üstlenen uygulamanın bu olduğunu unutmayın - örneğin, uygulama çalışma zamanında ilişkiyi güncellediğinde çift yönlü bir ilişkinin "bir" ve "çok" taraflarının birbiriyle tutarlı olmasını sağlamak için .


Ayrıntılı açıklama için çok teşekkür ederim! Örnek, noktaya ve ilk çalıştırmada işe yaradı.
sanjayav

@sunnyj Yardımcı olmaktan memnunum. Projelerinizde iyi şanslar.
Dan Larocque

Bu sorunu daha önce alt kategorileri olan kategori varlığı oluştururken karşıladı. Yardımcı olur!
Truong Ha

@DanLaRocque belki yanlış anlıyorum (veya bir varlık eşleme hatası alıyorum), ancak beklenmedik davranışlar görüyorum. Kullanıcı ve Adres arasında bire çok ilişkim var. Mevcut bir Kullanıcı bir Adres eklediğinde, hem Kullanıcıyı hem de Adresi güncelleme (ve her ikisinde de 'kaydet' seçeneğini çağırma) önerinize uydum. Ancak bu, Adres tabloma yinelenen bir satırın eklenmesine neden oldu. Bunun nedeni, CascadeType'ımı Kullanıcının adres alanında yanlış yapılandırmış olmam mı?
Alex

@DanLaRocque bu ilişkiyi tek yönlü olarak tanımlamak mümkün mü?
Ali Arda Orhan

8

Benim için işin püf noktası çoka çok ilişkisini kullanmaktı. A varlığınızın alt bölümlere sahip olabilen bir bölüm olduğunu varsayalım. Sonra (alakasız ayrıntıları atlayarak):

@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;
  }
...
}

Hiyerarşik yapı etrafında bazı kapsamlı iş mantığım olduğundan ve JPA (ilişkisel modele dayalı) onu desteklemek için çok zayıf olduğundan, arayüz IHierarchyElementve varlık dinleyicisini tanıttım 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);
        }
    }

}

1
Neden kendine referans veren özniteliklerle @ManyToMany (...) yerine daha basit @OneToMany (mappedBy = "DIV_PARENT_ID") kullanılmasın? Tablo ve sütun adlarının bu şekilde yeniden yazılması DRY'yi ihlal ediyor. Belki bunun bir nedeni vardır, ama ben görmüyorum. Ayrıca, EntityListener örneği düzgündür, ancak Topbir ilişki olduğu varsayılarak taşınabilir değildir . JPA 2.0 spesifikasyonu, Varlık Dinleyicileri ve Geri Çağırma Yöntemlerinin 93. Sayfası: "Genel olarak, taşınabilir bir uygulamanın yaşam döngüsü yöntemi EntityManager veya Sorgu işlemlerini çağırmamalı, diğer varlık örneklerine erişmemeli veya ilişkileri değiştirmemelidir". Sağ? İzin verirsem haberim olsun.
Dan LaRocque

Benim çözümüm JPA 1.0 kullanarak 3 yaşında. Üretim kodundan değiştirmeden uyarladım. Eminim bazı sütun isimlerini çıkarabilirim ama konu bu değildi. Cevabınız tam olarak ve daha basit, o zamanlar neden çoktan çoğa kullandığımdan emin değilim - ama işe yarıyor ve daha karmaşık bir çözümün bir nedenden ötürü orada olduğuna eminim. Yine de, şimdi buna tekrar bakmam gerekecek.
topchef

Evet, top bir öz referans, dolayısıyla bir ilişkidir. Açıkçası, onu değiştirmiyorum - sadece başlat. Ayrıca, tek yönlüdür, yani tam tersi bir bağımlılık yoktur, kendinin yanı sıra diğer varlıklara gönderme yapmaz. Teklifinize göre "genel olarak" ifadesi vardır, bu da kesin bir tanım olmadığı anlamına gelir. Bu durumda varsa çok düşük taşınabilirlik riski olduğuna inanıyorum.
topchef
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.