İki yönlü bir JPA OneToMany / ManyToOne ilişkilendirmesinde “ilişkilendirmenin ters tarafı” nedir?


167

@OneToManyJPA ek açıklama referansının örnek bölümünde :

Örnek 1-59 @OneToMany - Jenerikli Müşteri Sınıfı

@Entity
public class Customer implements Serializable {
    ...
    @OneToMany(cascade=ALL, mappedBy="customer")
    public Set<Order> getOrders() { 
        return orders; 
    }
    ...
}

Örnek 1-60 @ManyToOne - Jenerikli Sipariş Sınıfı

@Entity
public class Order implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="CUST_ID", nullable=false)
    public Customer getCustomer() { 
        return customer; 
    }
    ...
}

Bana öyle geliyor ki Customerkuruluş derneğin sahibidir. Ancak, mappedByaynı belgedeki öznitelik açıklamasında şöyle yazılır:

ilişki çift yönlü ise, ilişkilendirmenin ters (sahip olmayan) tarafında mappedBy öğesini Örnek 1-60'ın gösterdiği gibi ilişkiye sahip alan veya özellik adına ayarlayın.

Ancak, yanılmıyorsam, örnekteki gibi görünüyor, mappedBy , aslında sahip olmayan taraftan ziyade derneğin sahibi tarafında belirtilir.

Yani sorum temel olarak:

  1. İki yönlü (bir-çok / çok-bire) ilişkide, sahibi hangi varlıklardır? Bir tarafı nasıl sahip olarak atayabiliriz? Birçok tarafı nasıl sahip olarak atayabiliriz?

  2. "Derneğin ters tarafı" ile kastedilen nedir? Bir tarafı nasıl ters olarak tanımlayabiliriz? Birçok tarafı nasıl ters olarak tanımlayabiliriz?


1
sağladığınız bağlantı eski. Lütfen güncelle.
MartinL

Yanıtlar:


306

Bunu anlamak için geri adım atmalısınız. OO'da, müşteri siparişlerin sahibidir (siparişler müşteri nesnesindeki bir listedir). Müşteri olmadan sipariş olamaz. Yani müşteri siparişlerin sahibi gibi görünüyor.

Ancak SQL dünyasında, bir öğe aslında diğerine bir işaretçi içerecektir. N sipariş için 1 müşteri olduğundan, her sipariş ait olduğu müşteri için yabancı bir anahtar içerir. Bu "bağlantı" dır ve bu, bağlantının (bilgi) "sahibi" (veya tam anlamıyla içerdiği) anlamına gelir. Bu, OO / model dünyasının tam tersidir.

Bu anlamanıza yardımcı olabilir:

public class Customer {
     // This field doesn't exist in the database
     // It is simulated with a SQL query
     // "OO speak": Customer owns the orders
     private List<Order> orders;
}

public class Order {
     // This field actually exists in the DB
     // In a purely OO model, we could omit it
     // "DB speak": Order contains a foreign key to customer
     private Customer customer;
}

Ters taraf, nesnenin OO "sahibi" dir, bu durumda müşteri. Müşterinin siparişleri saklamak için tabloda sütunları olmadığından, sipariş tablosunda bu verileri nereye kaydedebileceğini (hangi yolla mappedBy) kaydedebileceğini söylemelisiniz .

Diğer bir yaygın örnek, hem ebeveyn hem de çocuk olabilen düğümlü ağaçlar. Bu durumda, iki alan bir sınıfta kullanılır:

public class Node {
    // Again, this is managed by Hibernate.
    // There is no matching column in the database.
    @OneToMany(cascade = CascadeType.ALL) // mappedBy is only necessary when there are two fields with the type "Node"
    private List<Node> children;

    // This field exists in the database.
    // For the OO model, it's not really necessary and in fact
    // some XML implementations omit it to save memory.
    // Of course, that limits your options to navigate the tree.
    @ManyToOne
    private Node parent;
}

Bu, "yabancı anahtar" çoktan bire tasarım çalışmalarını açıklar. İlişkileri sürdürmek için başka bir tablo kullanan ikinci bir yaklaşım var. Bu, ilk örneğimiz için üç tablonuz olduğu anlamına gelir: Müşterileri olan, siparişleri olan ve çift anahtar içeren iki sütunlu bir tablo (customerPK, orderPK).

Bu yaklaşım yukarıdakinden daha esnektir (bire bir, çoktan bire, bire çok ve hatta çoktan çoğa kolayca işleyebilir). Fiyat

  • biraz daha yavaştır (başka bir tabloyu korumak ve birleştirmek zorunda kalmak sadece iki yerine üç tablo kullanır)
  • birleştirme sözdizimi daha karmaşıktır (örneğin, bir şeyde hata ayıklamaya çalıştığınızda birçok sorguyu el ile yazmanız gerekirse sıkıcı olabilir)
  • bağlantı tablosunu yöneten kodda bir sorun olduğunda aniden çok fazla veya çok az sonuç alabilirsiniz.

Bu yüzden nadiren bu yaklaşımı öneriyorum.


36
Sadece açıklığa kavuşturmak için: birçok tarafın sahibi; bir tarafı tersidir. Seçeneğin yok (pratik olarak konuşuyor).
John

11
Hayır, Hibernate bunu icat etti. Uygulamanın bir kısmını OO modeline maruz bıraktığı için beğenmiyorum. Bağlantının ne anlama geldiğini (nasıl uygulandığını değil ) belirtmek için "XtoY" yerine bir @Parentveya @Childek açıklama tercih ederim
Aaron Digulla

4
@AaronDigulla Her zaman bir OneToMany eşlemelerinden geçmek zorunda kaldım, bu cevabı okumak için geldim, muhtemelen SO konusunda en iyisi.
Eugene

7
Vay. Sadece ORM çerçeve belgelerinin böyle iyi bir açıklaması olsaydı - her şeyi yutmayı kolaylaştırırdı! Mükemmel cevap!
NickJ

2
@klausch: Hazırda Bekletme belgeleri kafa karıştırıcı. Boşver. Koda, veritabanındaki SQL'e ve yabancı anahtarların nasıl çalıştığına bakın. İsterseniz, bir bilgelik evine götürebilirsiniz: Dokümantasyon bir yalandır. Kaynağı kullan, Luke.
Aaron Digulla

41

İnanılmaz bir şekilde, 3 yıl içinde hiç kimse mükemmel sorunuzu, ilişkiyi haritalamanın her iki yolunun örnekleriyle cevaplamamıştır.

Başkaları tarafından belirtildiği gibi, "sahip" tarafı veritabanında işaretçi (yabancı anahtar) içerir. Her iki tarafı da sahip olarak atayabilirsiniz, ancak, Bir tarafı sahibi olarak belirlerseniz, ilişki çift yönlü olmaz (ters aka "birçok" tarafın "sahibi" hakkında hiçbir bilgisi olmaz). Bu, kapsülleme / gevşek bağlantı için arzu edilebilir:

// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
    @OneToMany(cascade = CascadeType.ALL)
    private List<Order> orders;
}

// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
    // @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}

Tek yönlü eşleme çözümü, "çok" tarafının işaretçisini "bir" e sahip olması ve @OneToMany "mappedBy" özniteliğini kullanmasıdır. "MappedBy" özniteliği olmadan Hibernate çift eşleme bekler (veritabanında hem birleştirme sütunu hem de birleştirme tablosu bulunur; bu da gereksizdir (genellikle istenmeyen)).

// "One" Customer as the inverse side of the relationship
public class Customer {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
    private List<Order> orders;
}

// "many" orders each own their pointer to a Customer
public class Order {
    @ManyToOne
    private Customer customer;
}

2
Tek yönlü örneğinizde, JPA ek bir customer_orders tablosunun var olmasını bekler. JPA2 ile, Sipariş tablosunda kullanılması gereken veritabanı yabancı anahtar sütununu belirtmek için Müşterinin siparişler alanında @JoinColumn ek açıklamasını (sık kullandığım gibi) kullanabilirsiniz. Bu şekilde, Sipariş tablosunda yabancı anahtar sütunu varken Java'da tek yönlü bir ilişkiniz olur. Bu nedenle, Nesne dünyasında, Sipariş müşteriyi bilmiyor, veritabanı dünyasında ise Müşteri, Sipariş'i bilmiyor.
Henno Vermeulen

1
Uber-tam olmak için, müşterinin ilişkinin sahibi olduğu çift yönlü durumu gösterebilirsiniz.
HDave

35

Veritabanında yabancı anahtarlı tabloya sahip olan işletme, sahibi olan varlıktır ve diğer tablo ise ters varlıktır.


30
daha da basit: Sahibi FK Sütunu ile tablo
jacktrades

2
Basit ve iyi bir açıklama. Herhangi bir taraf Sahibi yapılabilir. MappedBy'yi Order.java'da kullanırsak, <mappedby'yi Customer.java'dan çıkar> Müşteri alanında, Order_Customer gibi 2 sütuna sahip yeni bir tablo oluşturulur. ORDER_ID ve CUSTOMER_ID.
HakunaMatata

14

Çift yönlü ilişkilerin basit kuralları:

1.Çoktan bire iki yönlü ilişkiler için, birçok taraf her zaman ilişkinin sahibi taraftır. Örnek: 1 Odanın birçok Kişisi vardır (Bir Kişi sadece bir Odaya aittir) -> sahip olunan Kişi

Bire bir çift yönlü ilişkiler için, sahip olan taraf ilgili yabancı anahtarı içeren tarafa karşılık gelir.

3.Çoktan çokya çift yönlü ilişkiler için, her iki tarafın sahibi taraf olabilir.

Umut size yardımcı olabilir.


Neden bir sahibi ve tersine ihtiyacımız var? Bir tarafın ve çok tarafın anlamlı kavramlarına zaten sahibiz ve çoktan çok durumda kimin sahibi olduğu önemli değil. Kararın sonuçları nelerdir? Bir veritabanı mühendisi olarak sol beyinli birinin bu gereksiz kavramları paraya çevirmeye karar verdiğine inanmak zor.
Dan Cancro

3

İki Varlık Sınıfı Müşteri ve Sipariş için hazırda bekletme modu iki tablo oluşturur.

Olası Durumlar:

  1. mappedBy, Customer.java ve Order.java Sınıflarında kullanılmaz.

    Müşteri tarafında, CUSTOMER_ID ve ORDER_ID eşlemelerini sürdürecek yeni bir tablo [name = CUSTOMER_ORDER] oluşturulacak. Bunlar Müşteri ve Sipariş Tablolarının birincil anahtarlarıdır. Sipariş tarafında, karşılık gelen Customer_ID kayıt eşlemesini kaydetmek için ek bir sütun gereklidir.

  2. mappedBy Customer.java'da kullanılıyor [Sorun bildiriminde verildiği gibi] Şimdi ek tablo [CUSTOMER_ORDER] oluşturulmadı. Sipariş Tablosunda yalnızca bir sütun

  3. mappedby Order.java içinde kullanılır Şimdi hazırda bekletme moduyla ek tablo oluşturulacak. [name = CUSTOMER_ORDER] Sipariş Tablosunun eşleme için [Customer_ID] sütunu olmayacak.

Herhangi bir Taraf ilişkinin sahibi yapılabilir. Ama xxxToOne tarafını seçmek daha iyi.

Kodlama efekti -> Varlığın yalnızca sahibi tarafı ilişki durumunu değiştirebilir. Aşağıdaki örnekte BoyFriend sınıfı ilişkinin sahibidir. Girlfriend ayrılmak istese bile yapamaz.

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "BoyFriend21")
public class BoyFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Boy_ID")
    @SequenceGenerator(name = "Boy_ID", sequenceName = "Boy_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "BOY_NAME")
    private String name;

    @OneToOne(cascade = { CascadeType.ALL })
    private GirlFriend21 girlFriend;

    public BoyFriend21(String name) {
        this.name = name;
    }

    public BoyFriend21() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BoyFriend21(String name, GirlFriend21 girlFriend) {
        this.name = name;
        this.girlFriend = girlFriend;
    }

    public GirlFriend21 getGirlFriend() {
        return girlFriend;
    }

    public void setGirlFriend(GirlFriend21 girlFriend) {
        this.girlFriend = girlFriend;
    }
}

import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;

@Entity 
@Table(name = "GirlFriend21")
public class GirlFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Girl_ID")
    @SequenceGenerator(name = "Girl_ID", sequenceName = "Girl_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "GIRL_NAME")
    private String name;

    @OneToOne(cascade = {CascadeType.ALL},mappedBy = "girlFriend")
    private BoyFriend21 boyFriends = new BoyFriend21();

    public GirlFriend21() {
    }

    public GirlFriend21(String name) {
        this.name = name;
    }


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public GirlFriend21(String name, BoyFriend21 boyFriends) {
        this.name = name;
        this.boyFriends = boyFriends;
    }

    public BoyFriend21 getBoyFriends() {
        return boyFriends;
    }

    public void setBoyFriends(BoyFriend21 boyFriends) {
        this.boyFriends = boyFriends;
    }
}


import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.Arrays;

public class Main578_DS {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
         try {
             configuration.configure("hibernate.cfg.xml");
         } catch (HibernateException e) {
             throw new RuntimeException(e);
         }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session = sessionFactory.openSession();
        session.beginTransaction();

        final BoyFriend21 clinton = new BoyFriend21("Bill Clinton");
        final GirlFriend21 monica = new GirlFriend21("monica lewinsky");

        clinton.setGirlFriend(monica);
        session.save(clinton);

        session.getTransaction().commit();
        session.close();
    }
}

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.List;

public class Main578_Modify {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
        try {
            configuration.configure("hibernate.cfg.xml");
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session1 = sessionFactory.openSession();
        session1.beginTransaction();

        GirlFriend21 monica = (GirlFriend21)session1.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        BoyFriend21 boyfriend = monica.getBoyFriends();
        System.out.println(boyfriend.getName()); // It will print  Clinton Name
        monica.setBoyFriends(null); // It will not impact relationship

        session1.getTransaction().commit();
        session1.close();

        final Session session2 = sessionFactory.openSession();
        session2.beginTransaction();

        BoyFriend21 clinton = (BoyFriend21)session2.load(BoyFriend21.class,10);  // Bill clinton record

        GirlFriend21 girlfriend = clinton.getGirlFriend();
        System.out.println(girlfriend.getName()); // It will print Monica name.
        //But if Clinton[Who owns the relationship as per "mappedby" rule can break this]
        clinton.setGirlFriend(null);
        // Now if Monica tries to check BoyFriend Details, she will find Clinton is no more her boyFriend
        session2.getTransaction().commit();
        session2.close();

        final Session session3 = sessionFactory.openSession();
        session1.beginTransaction();

        monica = (GirlFriend21)session3.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        boyfriend = monica.getBoyFriends();

        System.out.println(boyfriend.getName()); // Does not print Clinton Name

        session3.getTransaction().commit();
        session3.close();
    }
}

1

Tablo ilişkileri ve varlık ilişkileri

İlişkisel veritabanı sisteminde, yalnızca üç tür tablo ilişkisi olabilir:

  • bire çok (Yabancı Anahtar sütunu ile)
  • bire bir (paylaşılan Birincil Anahtar aracılığıyla)
  • çoktan çoğa (iki ayrı ana tabloya başvuran iki Yabancı Anahtar içeren bir bağlantı tablosu aracılığıyla)

Yani, bir one-to-manytablo ilişkisi şöyle görünür:

<code> bire çok </code> tablo ilişkisi

İlişkinin post_idalt tabloda Yabancı Anahtar sütununa (ör. ) Dayandığını unutmayın.

Dolayısıyla, bir one-to-manytablo ilişkisini yönetmek söz konusu olduğunda tek bir gerçek kaynağı vardır .

Şimdi, one-to-manydaha önce gördüğümüz tablo ilişkisini eşleyen iki yönlü bir varlık ilişkisi alırsanız :

Çift yönlü <code> Bire Çok </code> varlık ilişkisi

Yukarıdaki şemaya bakarsanız, bu ilişkiyi yönetmenin iki yolu olduğunu görebilirsiniz.

Gelen Postvarlık, sahip commentskoleksiyon:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

Ve içinde PostComment, postaşağıdaki gibi ilişki eşleştirilir:

@ManyToOne(
    fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;

Varlık birliğini değiştirebilecek iki tarafınız var:

  • Alt commentskoleksiyona bir girdi ekleyerek post_comment, üst postvarlık ile post_idsütunu aracılığıyla yeni bir satır ilişkilendirilmelidir .
  • Ayarlayarak postözelliği PostCommentvarlık, post_idsütun sıra güncellenmelidir.

Yabancı Anahtar sütununu temsil etmenin iki yolu olduğundan, ilişkilendirme durumu değişikliğini eşdeğer Yabancı Anahtar sütun değeri değişikliğine çevirirken, gerçeğin kaynağı hangisi olduğunu tanımlamanız gerekir.

MappedBy (ters taraf olarak da bilinir)

mappedByNitelik söyler @ManyToOneyan Yabancı anahtar sütunu yönetiminden sorumlu olduğunu ve toplama çocuk varlığı getirilemiyor sadece kullanıldığı ve çocuklara kaskad ana varlık devlet değişikliklere (örneğin aynı zamanda çocuk varlıkları kaldırmalısınız ebeveyn kaldırma).

Buna ters taraf denir, çünkü bu tablo ilişkisini yöneten alt varlık özelliğine başvurur.

İki yönlü bir ilişkilendirmenin her iki tarafını senkronize etme

Şimdi, mappedByözelliği tanımlasanız ve alt taraf @ManyToOneilişkilendirmesi Yabancı Anahtar sütununu yönetse bile, çift yönlü ilişkilendirmenin her iki tarafını da eşitlemeniz gerekir.

Bunu yapmanın en iyi yolu şu iki yardımcı program yöntemini eklemektir:

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

addCommentVe removeCommentyöntemler her iki taraf senkronize olduğundan emin olun. Dolayısıyla, bir alt varlık eklersek, alt öğenin ana öğeye işaret etmesi gerekir ve ana öğenin alt öğenin alt öğe koleksiyonunda bulunması gerekir.

Tüm çift yönlü varlık ilişkilendirme türlerini senkronize etmenin en iyi yolu hakkında daha fazla bilgi için bu makaleye göz atın .

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.