एक द्विदिश JPA OneToMany / ManyToOne एसोसिएशन में "एसोसिएशन का उलटा पक्ष" क्या है?


167

@OneToManyजेपीए एनोटेशन संदर्भ के उदाहरण अनुभाग में :

उदाहरण 1-59 @OneToMany - जेनरिक के साथ ग्राहक वर्ग

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

उदाहरण 1-60 @ManyToOne - जेनरिक के साथ ऑर्डर क्लास

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

मुझे ऐसा लगता है कि Customerसंस्था संघ का मालिक है। हालाँकि, mappedByएक ही दस्तावेज़ में विशेषता के लिए स्पष्टीकरण में, यह लिखा है कि:

यदि संबंध द्विदिश है, तो मैप्डबी तत्व को एसोसिएशन के विपरीत (गैर-मालिक) पक्ष पर उस क्षेत्र या संपत्ति के नाम पर सेट करें जो संबंध 1-60 शो के रूप में संबंध का मालिक है।

हालांकि, अगर मैं गलत नहीं हूं, तो यह उदाहरण में दिखता है, mappedByवास्तव में एसोसिएशन के खुद के पक्ष में निर्दिष्ट किया जाता है, न कि गैर-स्वामित्व पक्ष के बजाय।

तो मेरा प्रश्न मूल रूप से है:

  1. एक अप्रत्यक्ष (एक-से-कई / कई-से-एक) संघ में, कौन सी संस्था का मालिक है? हम मालिक के रूप में वन साइड को कैसे नामित कर सकते हैं? हम मालिक के रूप में कई पक्ष कैसे नामित कर सकते हैं?

  2. "संघ के विलोम पक्ष" से क्या अभिप्राय है? हम एक पक्ष को व्युत्क्रम के रूप में कैसे नामित कर सकते हैं? हम उलटे के रूप में कई पक्ष कैसे नामित कर सकते हैं?


1
आपके द्वारा प्रदत्त लिंक पुराना है। कृपया अद्यतन करें।
मार्टिन एलएल

जवाबों:


306

इसे समझने के लिए, आपको एक कदम पीछे हटना होगा। OO में, ग्राहक आदेशों का मालिक होता है (ऑर्डर ग्राहक ऑब्जेक्ट में एक सूची है)। ग्राहक के बिना ऑर्डर नहीं हो सकता। इसलिए ग्राहक आदेशों का स्वामी प्रतीत होता है।

लेकिन SQL दुनिया में, एक आइटम में वास्तव में दूसरे के लिए एक सूचक होगा। चूंकि एन ऑर्डर के लिए 1 ग्राहक है, इसलिए प्रत्येक ऑर्डर में ग्राहक की एक विदेशी कुंजी होती है। यह "कनेक्शन" है और इसका मतलब है कि "मालिक" (या शाब्दिक अर्थ में) कनेक्शन (सूचना) है। यह OO / मॉडल की दुनिया से बिल्कुल विपरीत है।

इससे समझने में मदद मिल सकती है:

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

व्युत्क्रम वस्तु का OO "स्वामी" है, इस मामले में ग्राहक। ग्राहक के पास ऑर्डर स्टोर करने के लिए टेबल में कोई कॉलम नहीं है, इसलिए आपको यह बताना होगा कि ऑर्डर टेबल में यह डेटा (जो होता है mappedBy) को बचा सकता है ।

एक और आम उदाहरण नोड्स के साथ पेड़ हैं जो माता-पिता और बच्चे दोनों हो सकते हैं। इस स्थिति में, दो क्षेत्रों का उपयोग एक कक्षा में किया जाता है:

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

यह "विदेशी कुंजी" के लिए कई-से-एक डिज़ाइन कार्यों के बारे में बताता है। एक दूसरा दृष्टिकोण है जो संबंधों को बनाए रखने के लिए एक और तालिका का उपयोग करता है। इसका मतलब है कि, हमारे पहले उदाहरण के लिए, आपके पास तीन तालिकाएँ हैं: ग्राहकों के साथ एक, ऑर्डर के साथ एक और प्राथमिक कुंजियों के जोड़े के साथ एक दो-स्तंभ तालिका (customerPK, orderPK)।

यह दृष्टिकोण ऊपर वाले की तुलना में अधिक लचीला है (यह आसानी से एक-से-एक, कई-एक, एक-से-कई और यहां तक ​​कि कई-से-कई को संभाल सकता है)। कीमत यह है कि

  • यह थोड़ा धीमा है (एक और तालिका बनाए रखने और जुड़ने के लिए सिर्फ दो के बजाय तीन तालिकाओं का उपयोग होता है),
  • ज्वाइन सिंटैक्स अधिक जटिल है (जो आपको थकाऊ हो सकता है यदि आपको मैन्युअल रूप से कई प्रश्न लिखना है, उदाहरण के लिए जब आप किसी चीज़ को डीबग करने का प्रयास करते हैं)
  • यह अधिक त्रुटि प्रवण है क्योंकि आप अचानक कोड में कुछ गलत होने पर बहुत अधिक या बहुत कम परिणाम प्राप्त कर सकते हैं जो कनेक्शन तालिका का प्रबंधन करता है।

इसलिए मैं शायद ही कभी इस दृष्टिकोण की सलाह देता हूं।


36
बस स्पष्ट करने के लिए: कई पक्ष मालिक हैं; एक तरफ उलटा है। आपके पास कोई विकल्प नहीं है (व्यावहारिक रूप से बोलना)।
जॉन

11
नहीं, हाइबरनेट ने इसका आविष्कार किया था। मुझे यह पसंद नहीं है क्योंकि यह OO मॉडल के कार्यान्वयन का हिस्सा है। मैं यह बताने के लिए "XtoY" के बजाय एक @Parentया @Childएनोटेशन पसंद करूंगा कि कनेक्शन का अर्थ क्या है (इसके बजाय इसे कैसे लागू किया जाता है )
हारून डिगुल्ला

4
@AaronDigulla हर बार मुझे OneToMany मैपिंग से गुजरना पड़ता है जो मुझे इस उत्तर को पढ़ने के लिए आया था, शायद एसओ के विषय पर सबसे अच्छा।
यूजीन

7
वाह। अगर केवल ओआरएम फ्रेमवर्क के डॉक्यूमेंटेशन में इतनी अच्छी व्याख्या होती - तो यह पूरी चीज को निगलने में आसान हो जाता! बहुत बढ़िया जवाब!
NickJ

2
@क्लास्च: हाइबरनेट प्रलेखन भ्रमित कर रहा है। अनदेखी करो इसे। कोड को देखें, डेटाबेस में SQL और विदेशी कुंजियाँ कैसे काम करती हैं। यदि आप चाहें, तो आप ज्ञान का एक टुकड़ा घर ले जा सकते हैं: प्रलेखन एक झूठ है। स्रोत का उपयोग करें, ल्यूक।
एरॉन दिगुल्ला

41

अविश्वसनीय रूप से, 3 वर्षों में किसी ने भी आपके उत्कृष्ट प्रश्न का उत्तर नहीं दिया है रिश्ते को मैप करने के दोनों तरीकों के उदाहरणों के साथ।

जैसा कि दूसरों ने उल्लेख किया है, डेटाबेस में "स्वामी" पक्ष में सूचक (विदेशी कुंजी) होता है। आप मालिक के रूप में दोनों ओर नामित कर सकते हैं, हालांकि, यदि आप एक पक्ष को स्वामी के रूप में नामित करते हैं, तो संबंध द्विदिश नहीं होगा (उलटा उर्फ ​​"कई" पक्ष को इसके "मालिक" का कोई ज्ञान नहीं होगा)। यह एनकैप्सुलेशन / ढीली युग्मन के लिए वांछनीय हो सकता है:

// "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
}

एकमात्र द्विदिश मानचित्रण समाधान के लिए "कई" पक्ष अपने सूचक को "एक" के लिए है, और @OneToMany "मैप्डबी" विशेषता का उपयोग करें। "मैपेडबाय" विशेषता के बिना हाइबरनेट डबल मैपिंग की उम्मीद करेगा (डेटाबेस में ज्वाइन कॉलम और जॉइन टेबल दोनों होंगे, जो बेमानी है (आमतौर पर अवांछनीय))।

// "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
अपने यूनिडायरेक्शनल उदाहरण में, जेपीए को उम्मीद है कि एक अतिरिक्त customer_orders तालिका मौजूद है। JPA2 के साथ आप आदेश तालिका में डेटाबेस विदेशी कुंजी कॉलम को निरूपित करने के लिए ग्राहक के आदेश क्षेत्र पर @JoinColumn एनोटेशन (जो मुझे अक्सर उपयोग करना प्रतीत होता है) का उपयोग कर सकते हैं। इस प्रकार आपके पास जावा में एक यूनिडायरेक्शनल संबंध है जबकि ऑर्डर टेबल में एक विदेशी कुंजी कॉलम है। तो वस्तु दुनिया में, ऑर्डर ग्राहक के बारे में नहीं जानता है जबकि डेटाबेस की दुनिया में, ग्राहक ऑर्डर के बारे में नहीं जानता है।
हेन्नो वर्म्यूलेन

1
उबेर-पूर्ण होने के लिए, आप द्विदिश मामले को दिखा सकते हैं जहां ग्राहक रिश्ते का मालिक है।
एचडीव

35

जिस इकाई में डेटाबेस में विदेशी कुंजी के साथ तालिका है, वह स्वामित्व इकाई है और दूसरी तालिका, जिस पर इंगित किया जा रहा है, व्युत्क्रम इकाई है।


30
यहां तक ​​कि सरल: मालिक एफके कॉलम के साथ तालिका है
जैकट्रेड्स

2
सरल और अच्छी व्याख्या। किसी भी पक्ष को मालिक बनाया जा सकता है। यदि हम ग्राहक के क्षेत्र में ऑर्डर.जावा में मैप्डबी का उपयोग करते हैं, तो ग्राहक से कस्टमाइज्ड करें। जेएवी> फिर एक नई तालिका बनाई जाएगी जैसे ऑर्डर_कस्टमर जिसमें 2 कॉलम होंगे। ORDER_ID और CUSTOMER_ID।
हकुनामाता

14

द्विदिश संबंधों के सरल नियम:

1. कई-से-एक द्विदिश रिश्तों के लिए, कई पक्ष हमेशा रिश्ते के पक्ष में होते हैं। उदाहरण: 1 कक्ष में कई व्यक्ति होते हैं (एक व्यक्ति केवल एक कक्ष होता है) -> स्वयं का पक्ष व्यक्ति होता है

2. एक-से-एक द्विदिश संबंधों के लिए, सहयोगी पक्ष उस पक्ष से मेल खाता है जिसमें संबंधित विदेशी कुंजी शामिल है।

3. कई-से-कई द्विदिश संबंधों के लिए, दोनों पक्ष स्वयं के पक्ष हो सकते हैं।

आशा है कि आप मदद कर सकते हैं।


हमें एक स्वामी और एक व्युत्क्रम की आवश्यकता क्यों है? हमारे पास पहले से ही एक-पक्ष और कई-पक्ष की सार्थक अवधारणाएं हैं और इससे कोई फर्क नहीं पड़ता कि कई स्थितियों में मालिक कौन है। निर्णय के परिणाम क्या हैं? यह विश्वास करना कठिन है कि डेटाबेस इंजीनियर के रूप में किसी को छोड़ दिया गया है, इन निरर्थक अवधारणाओं को गढ़ने का फैसला किया है।
डैन कैंक्रो

3

दो एंटिटी क्लासेस कस्टमर और ऑर्डर के लिए, हाइबरनेट दो टेबल बनाएगा।

संभव मामले:

  1. mappedBy का उपयोग Customer.java और ऑर्डर.java क्लास में तब नहीं किया जाता है->

    ग्राहक पक्ष में एक नई तालिका बनाई जाएगी [name = CUSTOMER_ORDER] जो CUSTOMER_ID और ORDER_ID की मैपिंग करती रहेगी। ये ग्राहक और ऑर्डर टेबल्स की प्राथमिक कुंजी हैं। ऑर्डर साइड पर संबंधित Customer_ID रिकॉर्ड मैपिंग को सहेजने के लिए एक अतिरिक्त कॉलम आवश्यक है।

  2. mappedBy का उपयोग Customer.java में किया जाता है [जैसा कि समस्या कथन में दिया गया है] अब अतिरिक्त तालिका [CUSTOMER_ORDER] नहीं बनाई गई है। ऑर्डर टेबल में केवल एक कॉलम

  3. mappedby का उपयोग ऑर्डर.जावा में किया जाता है। अब अतिरिक्त तालिका हाइबरनेट द्वारा बनाई जाएगी। [name = CUSTOMER_ORDER] ऑर्डर टेबल में मैपिंग के लिए अतिरिक्त कॉलम [Customer_ID] नहीं होगा।

किसी भी पक्ष को रिश्ते का मालिक बनाया जा सकता है। लेकिन xxxToOne पक्ष को चुनना बेहतर है।

कोडिंग प्रभाव -> केवल ओबिनिंग पक्ष इकाई संबंध स्थिति बदल सकती है। नीचे दिए गए उदाहरण में बॉयफ्रेंड क्लास रिश्ते का मालिक है। भले ही गर्लफ्रेंड ब्रेक-अप करना चाहती हो, वह नहीं कर सकती।

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

तालिका संबंध बनाम इकाई संबंध

एक रिलेशनल डेटाबेस सिस्टम में, केवल तीन प्रकार के टेबल रिलेशनशिप हो सकते हैं:

  • एक से कई (एक विदेशी कुंजी कॉलम के माध्यम से)
  • एक-से-एक (एक साझा प्राथमिक कुंजी के माध्यम से)
  • कई-से-कई (दो अलग-अलग पैरेंट टेबल के संदर्भ वाली दो विदेशी कुंजियों वाली लिंक तालिका के माध्यम से)

इसलिए, one-to-manyतालिका संबंध निम्नानुसार दिखता है:

<code> एक-से-कई </ code> तालिका संबंध

ध्यान दें कि संबंध फॉरेन की कॉलम पर आधारित है (जैसे, post_id चाइल्ड टेबल में ) ।

इसलिए, जब यह प्रबंध करने की बात आती है, तो सत्य का एक स्रोत होता है one-to-many तालिका संबंध ।

अब, यदि आप एक द्विदिश इकाई संबंध लेते हैं जो नक्शे पर है one-to-many पहले देखे तालिका संबंध :

द्विदिश <कोड> एक-से-कई </ कोड> इकाई संघ

यदि आप ऊपर दिए गए आरेख पर एक नज़र डालते हैं, तो आप देख सकते हैं कि इस संबंध को प्रबंधित करने के दो तरीके हैं।

में Post इकाई, आपके पास commentsसंग्रह:

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

और इसमें PostComment , postएसोसिएशन निम्नानुसार मैप किया गया है:

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

तो, आपके पास दो पक्ष हैं जो इकाई संघ को बदल सकते हैं:

  • commentsबाल संग्रह में एक प्रविष्टि जोड़कर , एक नयाpost_comment पंक्ति postको इसके माध्यम से मूल इकाई के साथ जोड़ा जाना चाहिएpost_id स्तंभ के ।
  • की postसंपत्ति निर्धारित करकेPostCommentइकाई , post_idकॉलम को भी अपडेट किया जाना चाहिए।

क्योंकि फॉरेन की कॉलम को दर्शाने के दो तरीके हैं, इसलिए आपको यह परिभाषित करना चाहिए कि जब एसोसिएशन राज्य परिवर्तन को अपने समकक्ष फॉरेन के कॉलम वैल्यू मॉडिफिकेशन में बदलने की बात करता है, तो सच्चाई का स्रोत है।

मैपेडबी (उर्फ उलटा पक्ष)

mappedByविशेषता बताता है कि @ManyToOneपक्ष विदेशी कुंजी स्तंभ के प्रबंधन का कार्यभार में है, और संग्रह इकलौती संतान संस्थाओं को लाने के लिए प्रयोग किया जाता है बच्चों के लिए झरना मूल इकाई राज्य परिवर्तन करने के लिए (जैसे, माता पिता को दूर भी बच्चे संस्थाओं को दूर करना चाहिए)।

इसे प्रतिलोम पक्ष कहा जाता है क्योंकि यह बाल इकाई संपत्ति का संदर्भ देता है जो इस तालिका संबंध का प्रबंधन करता है।

एक द्विदिश संघ के दोनों पक्षों को सिंक्रनाइज़ करें

अब, भले ही आपने mappedByविशेषता को परिभाषित किया हो और चाइल्ड-साइड @ManyToOneएसोसिएशन विदेशी कुंजी कॉलम का प्रबंधन करता है, फिर भी आपको द्विदिश संघ के दोनों पक्षों को सिंक्रनाइज़ करने की आवश्यकता है।

इन दो उपयोगिता विधियों को जोड़ने का सबसे अच्छा तरीका है:

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

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

addCommentऔर removeCommentतरीकों सुनिश्चित करना है कि दोनों पक्षों सिंक्रनाइज़ किए जाते हैं। इसलिए, यदि हम एक बाल इकाई को जोड़ते हैं, तो बाल इकाई को माता-पिता को इंगित करने की आवश्यकता होती है और माता-पिता इकाई को बच्चे को बाल संग्रह में समाहित करना चाहिए।

सभी द्विदिश इकाई संघ प्रकारों को सिंक्रनाइज़ करने के सर्वोत्तम तरीके के बारे में अधिक विवरण के लिए, इस लेख को देखें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.