Cái gì là mặt trái của hiệp hội, trong một hiệp hội hai chiều JPA OneToMany / ManyToOne là gì?


167

Trong phần ví dụ của @OneToManytài liệu tham khảo chú thích JPA :

Ví dụ 1-59 @OneToMany - Lớp khách hàng có Generics

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

Ví dụ 1-60 @ManyToOne - Lớp đặt hàng với Generics

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

Dường như với tôi rằng Customerthực thể là chủ sở hữu của hiệp hội. Tuy nhiên, trong phần giải thích cho mappedBythuộc tính trong cùng một tài liệu, nó được viết rằng:

nếu mối quan hệ là hai chiều, thì đặt phần tử mappedBy ở phía nghịch đảo (không sở hữu) của liên kết thành tên của trường hoặc thuộc tính sở hữu mối quan hệ như Ví dụ 1-60 hiển thị.

Tuy nhiên, nếu tôi không sai, có vẻ như trong ví dụ, mappedBythực tế được chỉ định ở phía sở hữu của hiệp hội, thay vì phía không sở hữu.

Vì vậy, câu hỏi của tôi về cơ bản là:

  1. Trong một hiệp hội hai chiều (một-nhiều / nhiều-một), chủ thể nào là chủ sở hữu? Làm thế nào chúng ta có thể chỉ định một bên là chủ sở hữu? Làm thế nào chúng ta có thể chỉ định nhiều bên là chủ sở hữu?

  2. "Mặt trái của hiệp hội" nghĩa là gì? Làm thế nào chúng ta có thể chỉ định một bên là nghịch đảo? Làm thế nào chúng ta có thể chỉ định nhiều bên là nghịch đảo?


1
liên kết bạn cung cấp đã lỗi thời. Vui lòng cập nhật.
MartinL

Câu trả lời:


306

Để hiểu điều này, bạn phải lùi lại một bước. Trong OO, khách hàng sở hữu các đơn đặt hàng (đơn hàng là một danh sách trong đối tượng khách hàng). Không thể có một đơn đặt hàng mà không có khách hàng. Vì vậy, khách hàng dường như là chủ sở hữu của các đơn đặt hàng.

Nhưng trong thế giới SQL, một mục thực sự sẽ chứa một con trỏ tới mục kia. Vì có 1 khách hàng cho N đơn hàng, mỗi đơn hàng chứa một khóa ngoại đối với khách hàng mà nó thuộc về. Đây là "kết nối" và điều này có nghĩa là thứ tự "sở hữu" (hoặc nghĩa đen chứa) kết nối (thông tin). Điều này hoàn toàn ngược lại với thế giới mô hình / OO.

Điều này có thể giúp hiểu:

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

Mặt trái là "chủ sở hữu" của đối tượng, trong trường hợp này là khách hàng. Khách hàng không có cột trong bảng để lưu trữ các đơn đặt hàng, vì vậy bạn phải cho nó biết nơi nào trong bảng đơn hàng có thể lưu dữ liệu này (xảy ra thông qua mappedBy).

Một ví dụ phổ biến khác là những cây có các nút có thể là cả cha mẹ và con cái. Trong trường hợp này, hai trường được sử dụng trong một lớp:

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

Điều này giải thích cho thiết kế nhiều khóa "ngoại khóa". Có một cách tiếp cận thứ hai sử dụng một bảng khác để duy trì các mối quan hệ. Điều đó có nghĩa là, ví dụ đầu tiên của chúng tôi, bạn có ba bảng: Một bảng có khách hàng, một bảng có đơn hàng và bảng hai cột với các cặp khóa chính (customerPK, orderPK).

Cách tiếp cận này linh hoạt hơn phương pháp trên (nó có thể dễ dàng xử lý một-một, nhiều-một, một-nhiều và thậm chí nhiều-nhiều). Giá là

  • chậm hơn một chút (phải duy trì một bảng khác và tham gia sử dụng ba bảng thay vì chỉ hai bảng),
  • cú pháp nối phức tạp hơn (có thể tẻ nhạt nếu bạn phải tự viết nhiều truy vấn, ví dụ như khi bạn cố gắng gỡ lỗi một cái gì đó)
  • nó dễ bị lỗi hơn bởi vì bạn có thể đột nhiên nhận được quá nhiều hoặc quá ít kết quả khi có lỗi xảy ra trong mã quản lý bảng kết nối.

Đó là lý do tại sao tôi hiếm khi đề xuất phương pháp này.


36
Chỉ cần làm rõ: nhiều bên là chủ sở hữu; một bên là nghịch đảo. Bạn không có lựa chọn (nói thực tế).
Giăng

11
Không, Hibernate đã phát minh ra điều này. Tôi không thích nó vì nó cho thấy một phần của việc triển khai mô hình OO. Tôi muốn một @Parenthoặc @Childchú thích thay vì "XtoY" trạng thái gì kết nối phương tiện (chứ không phải là cách nó được thực hiện )
Aaron Digulla

4
@AaronDigulla mỗi khi tôi phải trải qua một ánh xạ OneToMany tôi đã đến để đọc câu trả lời này, có lẽ là tốt nhất về chủ đề trên SO.
Eugene

7
Ồ Nếu chỉ có tài liệu khung ORM có một lời giải thích tốt như vậy - nó sẽ làm cho toàn bộ điều dễ nuốt hơn! Câu trả lời tuyệt vời!
NickJ

2
@klausch: Tài liệu Hibernate khó hiểu. Bỏ mặc nó. Nhìn vào mã, SQL trong cơ sở dữ liệu và cách các khóa ngoại hoạt động. Nếu bạn muốn, bạn có thể mang một mảnh nhà khôn ngoan: Tài liệu là một lời nói dối. Sử dụng nguồn, Luke.
Aaron Digulla

41

Không thể tin được, trong 3 năm không ai trả lời câu hỏi xuất sắc của bạn bằng các ví dụ về cả hai cách để lập bản đồ mối quan hệ.

Như đã đề cập bởi những người khác, phía "chủ sở hữu" chứa con trỏ (khóa ngoại) trong cơ sở dữ liệu. Bạn có thể chỉ định một trong hai bên là chủ sở hữu, tuy nhiên, nếu bạn chỉ định Một bên là chủ sở hữu, mối quan hệ sẽ không có tính hai chiều (bên trái hay còn gọi là "nhiều bên" sẽ không có kiến ​​thức về "chủ sở hữu"). Điều này có thể được mong muốn cho đóng gói / khớp nối lỏng lẻo:

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

Giải pháp ánh xạ hai chiều duy nhất là có phía "nhiều" sở hữu con trỏ của nó tới "một" và sử dụng thuộc tính "mappedBy" @OneToMany. Nếu không có thuộc tính "mappedBy", Hibernate sẽ mong muốn ánh xạ kép (cơ sở dữ liệu sẽ có cả cột tham gia và bảng tham gia, là dự phòng (thường là không mong muốn)).

// "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
Trong ví dụ đơn hướng của bạn, JPA hy vọng sẽ tồn tại thêm một bảng khách hàng. Với JPA2, bạn có thể sử dụng chú thích @JoinColumn (mà tôi dường như thường sử dụng) trên trường đơn hàng của Khách hàng để biểu thị cột khóa ngoài cơ sở dữ liệu trong bảng Đơn hàng nên được sử dụng. Theo cách này, bạn có một mối quan hệ đơn hướng trong Java trong khi vẫn có một cột khóa ngoại trong bảng Đơn hàng. Vì vậy, trong thế giới Đối tượng, Đơn hàng không biết về Khách hàng trong khi ở thế giới cơ sở dữ liệu, Khách hàng không biết về Đơn hàng.
Henno Vermeulen

1
Để hoàn thiện, bạn có thể chỉ ra trường hợp hai chiều trong đó khách hàng là phía sở hữu của mối quan hệ.
HDave

35

Thực thể có bảng có khóa ngoại trong cơ sở dữ liệu là thực thể sở hữu và bảng khác, được trỏ vào, là thực thể nghịch đảo.


30
thậm chí đơn giản hơn: Chủ sở hữu là bảng có Cột FK
jacktrades

2
Giải thích đơn giản và tốt. Bất kỳ bên nào có thể được làm chủ sở hữu. Nếu chúng tôi sử dụng mappedBy trong Order.java, trên trường Khách hàng <Xóa mappedby khỏi Customer.java> thì một bảng mới sẽ được tạo ra một cái gì đó như Order_Customer sẽ có 2 cột. ORDER_ID và CUSTOMER_ID.
HakunaMatata

14

Quy tắc đơn giản của mối quan hệ hai chiều:

1.Đối với các mối quan hệ hai chiều một phía, nhiều bên luôn là phía sở hữu của mối quan hệ. Ví dụ: 1 Phòng có nhiều Người (một người chỉ thuộc một Phòng) -> bên sở hữu là Người

2.Đối với mối quan hệ hai chiều một phía, bên sở hữu tương ứng với bên có chứa khóa ngoại tương ứng.

3.Đối với nhiều mối quan hệ hai chiều, hai bên có thể là bên sở hữu.

Hy vọng có thể giúp bạn.


Tại sao chúng ta cần phải có một chủ sở hữu và một nghịch đảo ở tất cả? Chúng tôi đã có các khái niệm có ý nghĩa về một phía và nhiều phía và không quan trọng ai là chủ sở hữu trong nhiều tình huống. Hậu quả của quyết định là gì? Thật khó để tin rằng một người nào đó là một kỹ sư cơ sở dữ liệu đã quyết định bỏ tiền vào những khái niệm dư thừa này.
Dan Cancro

3

Đối với hai lớp Thực thể Khách hàng và Đơn hàng, ngủ đông sẽ tạo hai bảng.

Các trường hợp có thể xảy ra:

  1. mappedBy không được sử dụng trong lớp Customer.java và Order.java sau đó->

    Ở phía khách hàng, một bảng mới sẽ được tạo [name = CUSTOMER_ORDER] sẽ tiếp tục ánh xạ CUSTOMER_ID và ORDER_ID. Đây là các khóa chính của Bảng khách hàng và đơn hàng. Ở phía Đơn hàng, cần có một cột bổ sung để lưu ánh xạ bản ghi Customer_ID tương ứng.

  2. mappedBy được sử dụng trong Customer.java [Như được đưa ra trong tuyên bố vấn đề] Bây giờ bảng bổ sung [CUSTOMER_ORDER] chưa được tạo. Chỉ một cột trong Bảng thứ tự

  3. mappedby được sử dụng trong Order.java Bây giờ bảng bổ sung sẽ được tạo bởi hibernate. [name = CUSTOMER_ORDER] Bảng đơn hàng sẽ không có cột bổ sung [Customer_ID] để ánh xạ.

Bất kỳ bên nào có thể được làm chủ sở hữu của mối quan hệ. Nhưng tốt hơn là chọn bên xxxToOne.

Hiệu ứng mã hóa -> Chỉ bên sở hữu của thực thể mới có thể thay đổi trạng thái mối quan hệ. Trong ví dụ dưới đây, lớp BoyFriend là chủ sở hữu của mối quan hệ. ngay cả khi bạn gái muốn chia tay, cô ấy cũng không thể.

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

Mối quan hệ bảng so với mối quan hệ thực thể

Trong một hệ thống cơ sở dữ liệu quan hệ, chỉ có thể có ba loại mối quan hệ bảng:

  • một-nhiều (thông qua cột Khóa ngoài)
  • một đối một (thông qua Khóa chính được chia sẻ)
  • nhiều-nhiều (thông qua bảng liên kết có hai Khóa ngoại tham chiếu hai bảng cha riêng biệt)

Vì vậy, một one-to-manymối quan hệ bảng trông như sau:

Mối quan hệ bảng <code> một-nhiều </ code>

Lưu ý rằng mối quan hệ dựa trên cột Khóa ngoài (ví dụ post_id:) trong bảng con.

Vì vậy, có một nguồn sự thật duy nhất khi nói đến việc quản lý one-to-manymối quan hệ bảng.

Bây giờ, nếu bạn có một mối quan hệ thực thể hai chiều ánh xạ trên one-to-manymối quan hệ bảng mà chúng ta đã thấy trước đây:

Hiệp hội thực thể <code> một-nhiều </ code> hai chiều

Nếu bạn nhìn vào sơ đồ trên, bạn có thể thấy rằng có hai cách để quản lý mối quan hệ này.

Trong Postthực thể, bạn có commentsbộ sưu tập:

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

Và, trong PostComment, posthiệp hội được ánh xạ như sau:

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

Vì vậy, bạn có hai mặt có thể thay đổi liên kết thực thể:

  • Bằng cách thêm một mục trong commentsbộ sưu tập con, một post_commenthàng mới sẽ được liên kết với postthực thể cha mẹ thông qua post_idcột của nó .
  • Bằng cách đặt thuộc posttính của PostCommentthực thể, post_idcột cũng sẽ được cập nhật.

Vì có hai cách để biểu diễn cột Khóa ngoài, bạn phải xác định đâu là nguồn gốc của sự thật khi chuyển đổi trạng thái kết hợp thành sửa đổi giá trị cột Ngoại khóa tương đương.

MappedBy (còn gọi là mặt nghịch đảo)

Các mappedBythuộc tính nói rằng @ManyToOnebên chịu trách nhiệm quản lý các cột Ngoại Key, và bộ sưu tập chỉ được sử dụng để lấy các đối tượng trẻ em và để thác thay đổi trạng thái thực thể cha mẹ cho trẻ em (ví dụ, loại bỏ phụ huynh cũng nên loại bỏ các đối tượng trẻ em).

Nó được gọi là mặt nghịch đảo vì nó tham chiếu thuộc tính thực thể con quản lý mối quan hệ bảng này.

Đồng bộ hóa cả hai mặt của hiệp hội hai chiều

Bây giờ, ngay cả khi bạn đã xác định mappedBythuộc tính và @ManyToOneliên kết phía con quản lý cột Khóa ngoài, bạn vẫn cần đồng bộ hóa cả hai mặt của liên kết hai chiều.

Cách tốt nhất để làm điều đó là thêm hai phương thức tiện ích sau:

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

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

Các phương pháp addCommentremoveCommentđảm bảo rằng cả hai bên được đồng bộ hóa. Vì vậy, nếu chúng ta thêm một thực thể con, thực thể con cần trỏ đến cha mẹ và thực thể cha mẹ nên có con trong bộ sưu tập con.

Để biết thêm chi tiết về cách tốt nhất để đồng bộ hóa tất cả các loại kết hợp thực thể hai chiều, hãy xem bài viết này .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.