Sự khác biệt giữa @JoinColumn và mappedBy khi sử dụng liên kết JPA @OneToMany


516

Sự khác biệt giữa:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}

1
Đồng thời xem phía sở hữu trong câu hỏi ánh xạ ORM là gì để có lời giải thích thực sự tốt về các vấn đề liên quan.
dirkt

Câu trả lời:


545

Chú thích @JoinColumnchỉ ra rằng thực thể này là chủ sở hữu của mối quan hệ (nghĩa là: bảng tương ứng có một cột có khóa ngoại với bảng được tham chiếu), trong khi thuộc tính mappedBychỉ ra rằng thực thể ở bên này là nghịch đảo của mối quan hệ và chủ sở hữu cư trú trong thực thể "khác". Điều này cũng có nghĩa là bạn có thể truy cập vào bảng khác từ lớp mà bạn đã chú thích bằng "mappedBy" (mối quan hệ hai chiều hoàn toàn).

Đặc biệt, đối với mã trong câu hỏi, các chú thích chính xác sẽ trông như thế này:

@Entity
public class Company {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}

3
trong cả hai trường hợp, Chi nhánh có trường với Công ty id.
Mykhaylo Adamovych

3
Bảng công ty không có cột có khóa ngoại với bảng được tham chiếu - Chi nhánh có tham chiếu đến Công ty .. tại sao bạn lại nói "bảng tương ứng có một cột có khóa ngoại với bảng được tham chiếu"? Bạn có thể giải thích thêm một số xin vui lòng.
Mykhaylo Adamovych

13
@MykhayloAdamovych Tôi đã cập nhật câu trả lời của mình bằng mã mẫu. Lưu ý rằng đó là một sai lầm khi sử dụng @JoinColumntrongCompany
Óscar López

10
@MykhayloAdamovych: Không, điều đó thực sự không hoàn toàn đúng. Nếu Branchkhông có thuộc tính tham chiếu Company, nhưng bảng bên dưới có một cột, thì bạn có thể sử dụng @JoinTableđể ánh xạ nó. Đây là một tình huống bất thường, bởi vì bạn thường ánh xạ cột trong đối tượng tương ứng với bảng của nó, nhưng nó có thể xảy ra, và nó hoàn toàn hợp pháp.
Tom Anderson

4
Đây là một lý do khác để không thích ORM. Các tài liệu thường quá tinh ranh, và trong các cuốn sách của tôi, đây là sự uốn khúc trên lãnh thổ ma thuật quá nhiều. Tôi đã phải vật lộn với vấn đề này và khi theo dõi từng chữ một @OneToOne, các hàng con được cập nhật với một nullcột FKey của chúng tham chiếu đến cha mẹ.
Ashesh

225

@JoinColumncó thể được sử dụng trên cả hai mặt của mối quan hệ. Câu hỏi là về việc sử dụng @JoinColumn@OneToManybên (trường hợp hiếm). Và vấn đề ở đây là sao chép thông tin vật lý (tên cột) cùng với truy vấn SQL không được tối ưu hóa sẽ tạo ra một số UPDATEcâu lệnh bổ sung .

Theo tài liệu :

nhiều người (hầu như) luôn là chủ sở hữu của mối quan hệ hai chiều trong thông số JPA, nên mối quan hệ giữa nhiều người được chú thích bởi@OneToMany(mappedBy=...)

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Troopcó mối quan hệ hai chiều với nhiều mối quan hệ Soldierthông qua tài sản quân đội. Bạn không phải (không được) xác định bất kỳ ánh xạ vật lý nào ở mappedBybên cạnh.

Để ánh xạ một hai chiều với nhiều người, với phía one-to-many như phía bên sở hữu , bạn phải gỡ bỏ các mappedByphần tử và thiết lập nhiều đến một @JoinColumnnhư insertableupdatablefalse. Giải pháp này không được tối ưu hóa và sẽ tạo ra một số UPDATEbáo cáo bổ sung .

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

1
Tôi không thể tìm ra cách Troop có thể là chủ sở hữu trong đoạn mã thứ hai của bạn, Soldier vẫn là chủ sở hữu, vì nó chứa khóa ngoại liên quan đến Troop. (Tôi đang sử dụng mysql, tôi đã kiểm tra với cách tiếp cận của bạn).
Akhilesh

10
Trong ví dụ của bạn, chú thích mappedBy="troop"đề cập đến lĩnh vực nào?
Fractaliste

5
@Fractaliste chú thích mappedBy="troop"đề cập đến đội quân tài sản trong lớp Người lính. Trong mã ở trên, thuộc tính không hiển thị vì ở đây Mykhaylo đã bỏ qua nó, nhưng bạn có thể suy ra sự tồn tại của nó bằng getter getTroop (). Kiểm tra câu trả lời của Óscar López , nó rất rõ ràng và bạn sẽ nhận được điểm.
nicolimo86

1
Ví dụ này là lạm dụng đặc tả JPA 2. Nếu mục tiêu của tác giả là tạo mối quan hệ hai chiều thì nên sử dụng mappedBy ở phía phụ huynh và JoinColumn (nếu cần) ở phía con. Với cách tiếp cận được trình bày ở đây, chúng tôi đang nhận được 2 mối quan hệ đơn phương: OneToMany và ManyToOne độc ​​lập nhưng chỉ nhờ may mắn (nhiều hơn bằng cách sử dụng sai) hai mối quan hệ đó được xác định bằng cùng một khóa ngoại
aurelije

1
Nếu bạn đang sử dụng JPA 2.x, câu trả lời của tôi dưới đây sẽ sạch hơn một chút. Mặc dù tôi khuyên bạn nên thử cả hai tuyến và xem Hibernate làm gì khi nó tạo các bảng. Nếu bạn đang ở trong một dự án mới, hãy chọn bất kỳ thế hệ nào bạn nghĩ phù hợp với nhu cầu của bạn. Nếu bạn đang sử dụng cơ sở dữ liệu cũ và không muốn thay đổi cấu trúc, hãy chọn bất kỳ cơ sở nào phù hợp với lược đồ của bạn.
Snekse

65

Vì đây là một câu hỏi rất phổ biến, tôi đã viết bài viết này , trên đó câu trả lời này dựa trên.

Hiệp hội một-nhiều-hướng

Như tôi đã giải thích trong bài viết này , nếu bạn sử dụng @OneToManychú thích với @JoinColumn, thì bạn có một liên kết đơn hướng, giống như liên kết giữa Postthực thể cha mẹ và con PostCommenttrong sơ đồ sau:

Hiệp hội một-nhiều-hướng

Khi sử dụng liên kết một-nhiều-hướng, chỉ có phía cha mẹ ánh xạ liên kết.

Trong ví dụ này, chỉ có Postthực thể sẽ xác định một @OneToManyliên kết đến PostCommentthực thể con :

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

Hiệp hội một-nhiều-hai chiều

Nếu bạn sử dụng @OneToManyvới tập mappedBythuộc tính, bạn có một liên kết hai chiều. Trong trường hợp của chúng tôi, cả hai Postthực thể đều có một tập hợp các PostCommentthực thể con và PostCommentthực thể con có một tham chiếu trở lại Postthực thể cha , như được minh họa bằng sơ đồ sau:

Hiệp hội một-nhiều-hai chiều

Trong PostCommentthực thể, thuộc tính postthực thể được ánh xạ như sau:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

Lý do chúng tôi đặt fetchthuộc tính rõ ràng FetchType.LAZYlà vì theo mặc định, tất cả @ManyToOnevà các @OneToOneliên kết được tìm nạp một cách háo hức, điều này có thể gây ra sự cố truy vấn N + 1. Để biết thêm chi tiết về chủ đề này, hãy xem bài viết này .

Trong Postthực thể, commentshiệp hội được ánh xạ như sau:

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

Các mappedBythuộc tính của @OneToManytài liệu tham khảo chú thích các posttài sản ở trẻ em PostCommenttổ chức nào, và, theo cách này, Hibernate biết rằng sự liên kết hai chiều được điều khiển bởi các @ManyToOnebên, trong đó có trách nhiệm quản lý các giá trị cột Ngoại chính mối quan hệ bảng này được dựa trên.

Đối với một hiệp hội hai chiều, bạn cũng cần có hai phương thức tiện ích, như addChildremoveChild:

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

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

Hai phương pháp này đảm bảo rằng cả hai mặt của hiệp hội hai chiều không đồng bộ. Không đồng bộ hóa cả hai đầu, Hibernate không đảm bảo rằng các thay đổi trạng thái kết hợp sẽ lan truyền đến cơ sở dữ liệu.

Để biết thêm chi tiết về wat tốt nhất để đồng bộ hóa các hiệp hội hai chiều với JPA và Hibernate, hãy xem bài viết này .

Chọn cái nào?

Các theo một hướng @OneToManyliên kết không thực hiện rất tốt , vì vậy bạn nên tránh nó.

Bạn nên sử dụng hai chiều @OneToManysẽ hiệu quả hơn .


32

Chú thích được ánh xạBy lý tưởng nên luôn luôn được sử dụng trong phía Cha mẹ (lớp Công ty) của mối quan hệ hai chiều, trong trường hợp này phải là trong lớp Công ty chỉ vào biến thành viên 'công ty' của lớp Con (lớp Chi nhánh)

Chú thích @JoinColumn được sử dụng để chỉ định một cột được ánh xạ để tham gia một hiệp hội thực thể, chú thích này có thể được sử dụng trong bất kỳ lớp nào (Phụ huynh hoặc Trẻ em) nhưng không nên sử dụng lý tưởng ở một bên (trong lớp cha mẹ hoặc trong lớp Con trong cả hai) ở đây trong trường hợp này tôi đã sử dụng nó trong phần Child (lớp Branch) của mối quan hệ hai chiều chỉ ra khóa ngoại trong lớp Branch.

dưới đây là ví dụ làm việc:

lớp phụ huynh, công ty

@Entity
public class Company {


    private int companyId;
    private String companyName;
    private List<Branch> branches;

    @Id
    @GeneratedValue
    @Column(name="COMPANY_ID")
    public int getCompanyId() {
        return companyId;
    }

    public void setCompanyId(int companyId) {
        this.companyId = companyId;
    }

    @Column(name="COMPANY_NAME")
    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
    public List<Branch> getBranches() {
        return branches;
    }

    public void setBranches(List<Branch> branches) {
        this.branches = branches;
    }


}

lớp trẻ, chi nhánh

@Entity
public class Branch {

    private int branchId;
    private String branchName;
    private Company company;

    @Id
    @GeneratedValue
    @Column(name="BRANCH_ID")
    public int getBranchId() {
        return branchId;
    }

    public void setBranchId(int branchId) {
        this.branchId = branchId;
    }

    @Column(name="BRANCH_NAME")
    public String getBranchName() {
        return branchName;
    }

    public void setBranchName(String branchName) {
        this.branchName = branchName;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="COMPANY_ID")
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }


}

20

Tôi chỉ muốn nói thêm rằng @JoinColumnkhông phải lúc nào cũng phải liên quan đến vị trí thông tin vật lý như câu trả lời này cho thấy. Bạn có thể kết hợp @JoinColumnvới @OneToManyngay cả khi bảng cha không có dữ liệu bảng trỏ đến bảng con.

Cách xác định mối quan hệ OneToMany đơn hướng trong JPA

OneToMany đơn hướng, Không nghịch đảo ManyToOne, Không tham gia Bảng

Nó dường như chỉ có sẵn trong JPA 2.x+mặc dù. Nó hữu ích cho các tình huống mà bạn muốn lớp con chỉ chứa ID của cha mẹ, không phải là tài liệu tham khảo đầy đủ.


bạn đã đúng, hỗ trợ cho OneToMany một chiều mà không cần tham gia bảng được giới thiệu trong JPA2
aurelije

17

Tôi không đồng ý với câu trả lời được chấp nhận ở đây bởi Óscar López. Câu trả lời đó là không chính xác!

Nó KHÔNG @JoinColumnchỉ ra rằng thực thể này là chủ sở hữu của mối quan hệ. Thay vào đó, nó là @ManyToOnechú thích thực hiện điều này (trong ví dụ của mình).

Các chú thích mối quan hệ như @ManyToOne, @OneToMany@ManyToManynói với JPA / Hibernate để tạo ánh xạ. Theo mặc định, điều này được thực hiện thông qua Bảng tham gia riêng biệt.


@JoinColumn

Mục đích của @JoinColumnviệc tạo một cột tham gia nếu chưa tồn tại. Nếu có, thì chú thích này có thể được sử dụng để đặt tên cho cột tham gia.


MappedBy

Mục đích của MappedBytham số là hướng dẫn JPA: KHÔNG tạo bảng tham gia khác vì mối quan hệ đã được ánh xạ bởi thực thể đối diện của mối quan hệ này.



Hãy nhớ rằng: MappedBylà một thuộc tính của các chú thích mối quan hệ có mục đích là tạo ra một cơ chế liên quan đến hai thực thể mà theo mặc định chúng thực hiện bằng cách tạo một bảng tham gia. MappedBydừng quá trình đó theo một hướng.

Thực thể không sử dụng MappedByđược cho là chủ sở hữu của mối quan hệ vì các cơ chế của ánh xạ được quyết định trong lớp của nó thông qua việc sử dụng một trong ba chú thích ánh xạ đối với trường khóa ngoại. Điều này không chỉ xác định bản chất của ánh xạ mà còn hướng dẫn tạo bảng tham gia. Hơn nữa, tùy chọn triệt tiêu bảng tham gia cũng tồn tại bằng cách áp dụng chú thích @JoinColumn đối với khóa ngoại giữ nó bên trong bảng của thực thể chủ sở hữu thay thế.

Vì vậy, tóm lại: @JoinColumnhoặc tạo một cột tham gia mới hoặc đổi tên một cột hiện có; trong khi MappedBytham số hoạt động cộng tác với các chú thích mối quan hệ của lớp (con) khác để tạo ánh xạ thông qua bảng nối hoặc bằng cách tạo cột khóa ngoài trong bảng liên kết của thực thể chủ sở hữu.

Để minh họa cách làm MapppedByviệc, hãy xem xét mã dưới đây. Nếu MappedBytham số bị xóa, thì Hibernate thực sự sẽ tạo các bảng tham gia TWO! Tại sao? Bởi vì có sự đối xứng trong nhiều mối quan hệ nhiều-nhiều và Hibernate không có lý do để chọn một hướng so với hướng khác.

Do đó, chúng tôi sử dụng MappedByđể nói với Hibernate, chúng tôi đã chọn thực thể khác để ra lệnh ánh xạ mối quan hệ giữa hai thực thể.

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    private List<Drivers> drivers;
}

Thêm @JoinColumn (name = "driverID") trong lớp chủ sở hữu (xem bên dưới), sẽ ngăn việc tạo bảng tham gia và thay vào đó, tạo cột khóa ngoại trình driverID trong bảng Ô tô để tạo ánh xạ:

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}

1

JPA là một API lớp, các cấp độ khác nhau có chú thích riêng. Mức cao nhất là (1) Cấp thực thể mô tả các lớp liên tục sau đó bạn có mức cơ sở dữ liệu quan hệ (2) giả sử các thực thể được ánh xạ tới cơ sở dữ liệu quan hệ và (3) mô hình java.

Level 1 chú thích: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Bạn có thể giới thiệu tính bền bỉ trong ứng dụng của mình bằng cách sử dụng các chú thích cấp cao này. Nhưng sau đó, bạn phải tạo cơ sở dữ liệu của mình theo các giả định mà JPA đưa ra. Những chú thích này xác định mô hình thực thể / mối quan hệ.

Level 2 chú thích: @Table, @Column, @JoinColumn, ... ảnh hưởng của ánh xạ từ các tổ chức / tài sản để các bảng cơ sở dữ liệu quan hệ / cột nếu bạn không hài lòng với giá trị mặc định JPA hoặc nếu bạn cần để ánh xạ đến một cơ sở dữ liệu hiện có. Các chú thích này có thể được xem là chú thích thực hiện, chúng chỉ định cách thực hiện ánh xạ.

Theo tôi, tốt nhất là gắn càng nhiều càng tốt vào các chú thích cấp cao và sau đó giới thiệu các chú thích cấp thấp hơn khi cần thiết.

Để trả lời các câu hỏi: @OneToMany/ mappedBylà tốt nhất vì nó chỉ sử dụng các chú thích từ miền thực thể. Các @oneToMany/ @JoinColumncũng là tốt, nhưng nó sử dụng một chú thích thực hiện nơi này là không thực sự cần thiết.


1

Hãy để tôi làm cho nó đơn giản.
Bạn có thể sử dụng @JoinColumn ở cả hai bên không phân biệt ánh xạ.

Hãy chia điều này thành ba trường hợp.
1) Ánh xạ định hướng từ Chi nhánh đến Công ty.
2) Ánh xạ hai chiều từ Công ty đến Chi nhánh.
3) Chỉ ánh xạ định hướng Uni từ Công ty đến Chi nhánh.

Vì vậy, bất kỳ trường hợp sử dụng sẽ thuộc ba loại này. Vì vậy, hãy để tôi giải thích cách sử dụng @JoinColumnmappedBy .
1) Ánh xạ định hướng từ Chi nhánh đến Công ty.
Sử dụng JoinColumn trong bảng Chi nhánh.
2) Ánh xạ hai chiều từ Công ty đến Chi nhánh.
Sử dụng mappedBy trong bảng Công ty như được mô tả bởi câu trả lời của @Mykhaylo Adamovych.
3) Ánh xạ định hướng từ Công ty đến Chi nhánh.
Chỉ cần sử dụng @JoinColumn trong bảng Công ty.

@Entity
public class Company {

@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}

Điều này nói rằng dựa trên ánh xạ khóa học "khóa học" trong bảng chi nhánh, hãy cho tôi danh sách tất cả các chi nhánh. LƯU Ý: bạn không thể tìm nạp công ty từ chi nhánh trong trường hợp này, chỉ có ánh xạ đơn hướng từ công ty đến chi nhánh.

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.