使用JPA @OneToMany关联时,@ JoinColumn和mappedBy有什么区别


516

之间有什么区别?

@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
另请参阅ORM映射问题的所有者是什么,以获取对所涉及问题的很好解释。
dirkt

Answers:


545

批注@JoinColumn指示此实体是关系的所有者(即:对应的表具有一列,该列带有被引用表的外键),而属性mappedBy指示该侧的实体是关系的逆向,并且所有者居住在“其他”实体中。这也意味着您可以从用“ mappedBy”(完全双向关系)注释的类中访问另一个表。

特别是,对于问题中的代码,正确的注释应如下所示:

@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
在这两种情况下,分公司都有带有公司ID的字段。
Mykhaylo Adamovych 2012年

3
公司表中没有包含引用表的外键的列-分支引用了公司..为什么要说“相应的表具有包含引用表的外键的列”?您能再解释一下吗。
Mykhaylo Adamovych 2012年

13
@MykhayloAdamovych我用示例代码更新了我的答案。请注意,这是用一个错误@JoinColumnCompany
奥斯卡·洛佩斯

10
@MykhayloAdamovych:不,实际上这不太正确。如果Branch没有引用的属性Company,但是基础表具有引用的列,则可以使用@JoinTable它进行映射。这是一种不寻常的情况,因为您通常会在对象中映射与其表相对应的列,但是它可能发生,并且完全合法。
汤姆·安德森

4
这是不喜欢ORM的另一个原因。文档常常太晦涩难懂,在我的书中,这在太多的魔术领域上是曲折的。我一直在努力解决这个问题,当逐字逐字地搜索a时@OneToOne,子行在null引用父项的FKey列中使用a更新。
Ashesh'3

225

@JoinColumn可以在关系的两边使用。现在的问题是关于使用@JoinColumn@OneToMany侧(极少数情况下)。这里的重点是物理信息重复(列名)以及未优化的SQL查询,这会产生一些其他UPDATE语句

根据文件

由于多对一的(几乎)总是所有者侧的在JPA规范的双向关系中,一对多关联是通过注解@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() {
    ...
} 

TroopSoldier通过troop属性具有双向的一对多关系。您不必(不必)在mappedBy侧面定义任何物理映射。

以一对多方为拥有方映射双向一对多,您必须删除mappedBy元素并将多对数设置@JoinColumn为as insertableupdatablefalse。此解决方案未经过优化,将产生一些其他UPDATE语句。

@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
我无法弄清楚Troop如何成为您的第二个片段的所有者,Soldier仍然是所有者,因为它包含引用Troop的外键。(我正在使用mysql,我检查了您的方法)。
Akhilesh

10
在您的示例中,注释mappedBy="troop"指的是哪个字段?
Fractaliste 2014年

5
@Fractaliste注释mappedBy="troop"引用士兵类中的属性部队。在上面的代码中,该属性不可见,因为在此Mykhaylo省略了该属性,但是您可以通过getter getTroop()推断出该属性的存在。检查ÓscarLópez的答案,这很清楚,您会明白的。
nicolimo86

1
该示例滥用了JPA 2规范。如果作者的目的是创建双向关系,则应在父端使用mapledBy,在子端使用JoinColumn(如果需要)。通过这里介绍的方法,我们得到了2个单向关系:OneToMany和ManyToOne是独立的,但是只是靠运气(更多地是由于滥用),这2个关系是使用相同的外键定义的
aurelije 2016年

1
如果您使用的是JPA 2.x,我在下面的回答会更干净一些。尽管我建议尝试两条路径,然后看看Hibernate在生成表时会做什么。如果您在进行新项目,请选择您认为适合的一代。如果您使用的是旧数据库,并且不想更改结构,请选择与您的架构匹配的任何一个。
Snekse

65

由于这是一个非常常见的问题,因此我写了 这篇文章,此答案基于此。

单向一对多关联

正如我在本文中所解释的,如果将@OneToMany注释与一起使用@JoinColumn,则您将具有单向关联,例如下图中的父Post实体和子实体之间的关联PostComment

单向一对多关联

使用单向一对多关联时,只有父侧会映射该关联。

在此示例中,仅Post实体将定义与子实体的@OneToMany关联PostComment

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

双向一对多关联

如果@OneToManymappedBy属性集一起使用,则将具有双向关联。在我们的示例中,两个Post实体都有一个PostComment子实体集合,并且该PostComment子实体具有对父Post实体的引用,如下图所示:

双向一对多关联

PostComment实体中,post实体属性映射如下:

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

我们将fetch属性显式设置为的原因FetchType.LAZY是,由于默认情况下,所有@ManyToOne@OneToOne关联都被急切获取,这可能会导致N + 1查询问题。有关此主题的更多详细信息,请参阅本文

Post实体中,comments关联映射如下:

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

mappedBy该属性@OneToMany注释引用post属性在儿童PostComment实体,而且这样一来,Hibernate知道了双向关联是由控制@ManyToOne一面,这是负责管理这个表的关系是基于外键列值。

对于双向关联,您还需要具有两个实用程序方法,例如addChildremoveChild

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

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

这两种方法可确保双向关联的两端不同步。如果不同步两端,则Hibernate无法保证关联状态更改将传播到数据库。

有关使双向关联与JPA和Hibernate同步的最佳方法的更多详细信息,请参阅本文

选择哪一个?

单向@OneToMany协会不执行得很好,所以你应该避免。

您最好使用更高效双向@OneToMany


32

注释已映射理想情况下,始终应在双向关系的父级(公司类)中使用,在这种情况下,应在公司类中指向子类(分支类)的成员变量“ company”

注释@JoinColumn用于指定用于连接实体关联的映射列,该注释可以在任何类(父类或子类)中使用,但理想情况下仅应在一侧使用(在父类或子类中不使用)在这两种情况下,在这种情况下,我都在双向关系的子级(分支类)中使用了它,指示了分支类中的外键。

以下是工作示例:

家长班

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


}

子班,分支

@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

我只想补充一点,这个答案@JoinColumn并不总是与物理信息位置相关。即使父表没有指向子表的表数据,也可以与合并。@JoinColumn@OneToMany

如何在JPA中定义单向OneToMany关系

单向OneToMany,无反向ManyToOne,无联接表

它似乎仅在可用JPA 2.x+。对于希望子类仅包含父级ID而不是完整引用的情况,这很有用。


没错,在JPA2中引入了对无连接表的单向OneToMany的支持
aurelije 2016年

17

我不同意ÓscarLópez在这里接受的答案。这个答案不正确!

不是@JoinColumn表示该实体是关系的所有者。而是由@ManyToOne注释执行此操作(在他的示例中)。

的关系注释,比如@ManyToOne@OneToMany@ManyToMany告诉JPA / Hibernate来创建一个映射。 默认情况下,这是通过单独的联接表完成的。


@JoinColumn

的目的@JoinColumn是创建联接列(如果尚不存在)。如果是这样,则可以使用此批注来 命名联接列。


映射

MappedBy参数的目的是指示JPA:不要创建另一个联接表,因为该关系的相对实体已经映射了该关系。



请记住: MappedBy是关系批注的属性,其目的是生成一种机制,以关联两个实体,默认情况下,它们通过创建联接表来实现。 MappedBy沿一个方向停止该过程。

未使用的实体MappedBy被称为所有者关系,是因为映射的机制是通过使用针对外键字段的三个映射注释之一在其类内规定的。这不仅指定了映射的性质,还指示了联接表的创建。此外,通过在外键上应用@JoinColumn批注,也可以抑制联接表,该选项将其保留在所有者实体的表内。

因此,总而言之:@JoinColumn要么创建一个新的联接列,要么重命名一个现有的联接列;同时该MappedBy参数与其他(子)类的关系注释协同工作,以便通过联接表或通过在所有者实体的关联表中创建外键列来创建映射。

为了说明其MapppedBy工作原理,请考虑以下代码。如果MappedBy要删除参数,则Hibernate实际上将创建两个联接表!为什么?因为在多对多关系中存在对称性,并且Hibernate没有选择一个方向胜过另一个方向的理由。

因此MappedBy,我们习惯告诉Hibernate,我们选择了另一个实体来决定两个实体之间关系的映射。

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

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

在所有者类中添加@JoinColumn(name =“ driverID”)(请参见下文)将阻止创建联接表,而是在Cars表中创建driverID外键列以构建映射:

@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是分层API,不同级别具有各自的注释。最高级别是(1)实体级别,它描述持久性类,然后您具有(2)关系数据库级别,其中假定实体已映射到关系数据库,以及(3)Java模型。

1级注释:@Entity@Id@OneToOne@OneToMany@ManyToOne@ManyToMany。您可以仅使用这些高级注释在应用程序中引入持久性。但是随后您必须根据JPA所做的假设来创建数据库。这些注释指定实体/关系模型。

等级2注释:@Table@Column@JoinColumn,...影响从实体/属性映射到关系数据库表/如果你不满意JPA的默认值,或者如果你需要映射到现有的数据库列。这些注释可以视为实现注释,它们指定应如何完成映射。

在我看来,最好尽可能地坚持高级注释,然后根据需要引入低级注释。

回答问题:@OneToMany/ mappedBy最好,因为它仅使用来自实体域的注释。该@oneToMany/ @JoinColumn还不错,但它使用的是实现注解哪里,这不是绝对必要的。


1

让我简单点。无论映射如何,
都可以在任何一侧使用@JoinColumn

让我们将其分为三种情况。
1)从分支到公司的单向映射。
2)从公司到分支的双向映射。
3)仅从公司到分支的单向映射。

因此,任何用例都属于这三类。因此,让我解释如何使用@JoinColumn的mappedBy
1)从分支到公司的单向映射。在分支表中
使用JoinColumn
2)从公司到分支的双向映射。
使用的mappedBy在公司表按@Mykhaylo Adamovych的答案描述。
3)从公司到分支的单向映射。
只需在Company表中使用@JoinColumn

@Entity
public class Company {

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

这表示基于分支表中的外键“ courseId”映射,获取所有分支的列表。注意:在这种情况下,您无法从分支机构获取公司,只有公司与分支机构之间存在单向映射。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.