有什么区别:
@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;
...
}
注释 @JoinColumn
表示该实体是关系的所有者(即:对应表有一个列,该列具有引用表的外键),而属性 mappedBy
表示这边的实体是关系的逆,所有者驻留在“其他”实体中。这也意味着您可以从使用“mappedBy”(完全双向关系)注释的类中访问另一个表。
特别是,对于问题中的代码,正确的注释应如下所示:
@Entity
public class Company {
@OneToMany(mappedBy = "company",
orphanRemoval = true,
fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<Branch> branches;
}
@Entity
public class Branch {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "companyId")
private Company company;
}
@JoinColumn
可用于关系的双方。 问题是关于在 @OneToMany
方面使用 @JoinColumn
(极少数情况)。这里的重点在于物理信息重复(列名)以及未优化的 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() {
...
}
Troop
通过 troop 属性与 Soldier
具有双向的一对多关系。您不必(不得)在 mappedBy
端定义任何物理映射。
要将双向一对多映射,以 一对多作为拥有方,您必须删除 mappedBy
元素并将多对一 @JoinColumn
设置为 insertable
updatable
为假。此解决方案未优化,会产生一些额外的 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() {
...
}
mappedBy="troop"
指的是哪个字段?
mappedBy="troop"
指的是士兵类中的属性部队。在上面的代码中,该属性是不可见的,因为这里 Mykhaylo 省略了它,但您可以通过 getter getTroop() 推断它的存在。检查Óscar López的答案,很清楚,你会明白的。
单向一对多关联
如果您将 @OneToMany
注释与 @JoinColumn
一起使用,则您有一个单向关联,如下图中父 Post
实体和子 PostComment
实体之间的关联:
https://i.stack.imgur.com/ljaHg.png
使用单向一对多关联时,只有父方映射关联。
在此示例中,只有 Post
实体将定义与子 PostComment
实体的 @OneToMany
关联:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();
双向一对多关联
如果您将 @OneToMany
与 mappedBy
属性集一起使用,则您具有双向关联。在我们的例子中,Post
实体都有一个 PostComment
子实体的集合,并且子 PostComment
实体有一个对父 Post
实体的引用,如下图所示:
https://i.stack.imgur.com/i5YWM.png
在 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<>();
@OneToMany
注释的 mappedBy
属性引用了子 PostComment
实体中的 post
属性,这样,Hibernate 就知道双向关联是由 @ManyToOne
方控制的,它负责管理此表关系所基于的外键列值。
对于双向关联,您还需要有两个实用方法,例如 addChild
和 removeChild
:
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
这两种方法确保双向关联的双方是同步的。如果不同步两端,Hibernate 不保证关联状态更改会传播到数据库。
选择哪一个?
单向 @OneToMany
关联表现不佳,因此您应该避免使用它。
您最好使用效率更高的双向 @OneToMany
。
理想情况下,mappedBy 注释应该始终用于双向关系的 Parent 端(Company 类),在这种情况下,它应该在 Company 类中指向 Child 类(Branch 类)的成员变量“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;
}
}
我不同意 Óscar López 在这里接受的答案。这个答案不准确!
它不是 @JoinColumn
,表示该实体是关系的所有者。相反,执行此操作的是 @ManyToOne
注释(在他的示例中)。
@ManyToOne
、@OneToMany
和 @ManyToMany
等关系注释告诉 JPA/Hibernate创建映射。默认情况下,这是通过单独的 Join Table 完成的。
@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;
}
@JoinColumn
。
@JoinColumn
来避免连接表。用注解声明两边 + 用 mappedBy 声明两边也会引入这种行为。
我想补充一点,正如 this 答案所暗示的那样,@JoinColumn
并不总是必须与 物理信息位置 相关。即使父表没有指向子表的表数据,您也可以将 @JoinColumn
与 @OneToMany
组合使用。
How to define unidirectional OneToMany relationship in JPA
Unidirectional OneToMany, No Inverse ManyToOne, No Join Table
不过,它似乎只在 JPA 2.x+
中可用。当您希望子类只包含父类的 ID,而不是完整的参考时,这很有用。
让我简单点。无论映射如何,您都可以在任一侧使用 @JoinColumn。
让我们将其分为三种情况。 1)从分公司到公司的单向映射。 2) 从公司到分公司的双向映射。 3) 只有从公司到分公司的单向映射。
因此,任何用例都属于这三个类别。那么让我解释一下如何使用@JoinColumn 和mappedBy。 1)从分公司到公司的单向映射。在 Branch 表中使用 JoinColumn。 2) 从公司到分公司的双向映射。如@Mykhaylo Adamovych 的回答所述,在 Company 表中使用 mappedBy。 3)公司到分公司的单向映射。只需在 Company 表中使用 @JoinColumn。
@Entity
public class Company {
@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}
这表示基于分支表中的外键“courseId”映射,获取所有分支的列表。注意:在这种情况下,您无法从分支机构获取公司,从公司到分支机构仅存在单向映射。
JPA 是一个分层的 API,不同的层次都有自己的注解。最高级别是 (1) 描述持久类的实体级别,然后您有 (2) 关系数据库级别,假设实体映射到关系数据库和 (3) java 模型。
级注释:@Entity
、@Id
、@OneToOne
、@OneToMany
、@ManyToOne
、@ManyToMany
。您可以单独使用这些高级注释在应用程序中引入持久性。但是你必须根据 JPA 所做的假设来创建你的数据库。这些注释指定实体/关系模型。
2 级注释:@Table
、@Column
、@JoinColumn
、...如果您对 JPA 的默认设置不满意或需要映射到现有的数据库,则会影响从实体/属性到关系数据库表/列的映射数据库。这些注解可以看作是实现注解,它们指定应该如何进行映射。
在我看来,最好尽可能坚持高级注释,然后根据需要引入较低级别的注释。
回答问题:@OneToMany
/mappedBy
最好,因为它只使用实体域中的注释。 @oneToMany
/@JoinColumn
也可以,但它使用了非严格必要的实现注释。
Company
中使用@JoinColumn
是错误的Branch
没有引用Company
的属性,但基础表有一个引用Company
的列,那么您可以使用@JoinTable
来映射它。这是一种不寻常的情况,因为您通常会映射对象中与其表对应的列,但它可能会发生,而且是完全合法的。@OneToOne
时,子行在其引用父级的 FKey 列中更新为null
。