JPA JoinColumn vs mappedBy(JPA的JoinColumn与mappedBy) 在JPA中,JoinColumn和mappedBy是用于处理实体之间关联关系的两个重要注解。 JoinColumn用于在关系的拥有方(owning side)实体中指定外键列的属性。它可以在@OneToOne、@OneToMany和@ManyToOne注解中使用。 mappedBy用于在关系的非拥有方(inverse side)实体中指定关联关系的属性。它可以在@OneToOne、@OneToMa
JPA JoinColumn vs mappedBy(JPA的JoinColumn与mappedBy) 在JPA中,JoinColumn和mappedBy是用于处理实体之间关联关系的两个重要注解。 JoinColumn用于在关系的拥有方(owning side)实体中指定外键列的属性。它可以在@OneToOne、@OneToMany和@ManyToOne注解中使用。 mappedBy用于在关系的非拥有方(inverse side)实体中指定关联关系的属性。它可以在@OneToOne、@OneToMa
这两段代码之间有什么区别?
第一段代码中,通过使用@JoinColumn注解,将Company实体与Branch实体关联起来。通过设置name属性为"companyIdRef",将Company实体中的branches属性与Branch实体中的companyId属性进行关联。同时,设置referencedColumnName属性为"companyId",指定Branch实体中的companyId属性作为关联的参考列。
第二段代码中,通过使用@mappedBy注解,将Company实体与Branch实体关联起来。通过设置mappedBy属性为"companyIdRef",指定Branch实体中的companyIdRef属性作为关联的映射列。
JPA JoinColumn vs mappedBy
JPA是Java Persistence API的缩写,它是Java EE的一部分,提供了一种方便的方法来将Java对象映射到关系数据库中的表。在JPA中,有两种方式来定义实体之间的关联关系:Join Columns和MappedBy。本文将介绍这两种方式的原因和解决方法。
单向一对多关联
如果使用@JoinColumn
注解而不使用mappedBy
属性,则表示有一个单向关联关系,就像下图中父实体Post
和子实体PostComment
之间的关系一样:

在单向一对多关联中,只有父实体定义了关联关系。在这个例子中,只有Post
实体定义了对子实体PostComment
的@JoinColumn
关联关系:
(cascade = CascadeType.ALL, orphanRemoval = true) (name = "post_id") private Listcomments = new ArrayList<>();
双向一对多关联
如果使用@JoinColumn
注解并且设置了mappedBy
属性,则表示有一个双向关联关系。在我们的例子中,Post
实体有一个PostComment
子实体的集合,而PostComment
子实体有一个指向父实体Post
的引用,如下图所示:

在PostComment
实体中,post
属性的映射如下:
(fetch = FetchType.LAZY) private Post post;
需要明确设置fetch
属性为FetchType.LAZY
,因为默认情况下,所有@OneToMany
和@ManyToOne
关联关系都是急加载的,这可能会引发N+1查询问题。
在Post
实体中,comments
关联关系的映射如下:
( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true ) private Listcomments = new ArrayList<>();
@JoinColumn
注解的mappedBy
属性指向子实体PostComment
中的post
属性,这样Hibernate就知道双向关联关系由Post
一侧控制,负责管理基于外键列值的表关系。
对于双向关联关系,还需要两个辅助方法,比如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不能保证关联状态的改变会传播到数据库。
选择哪种关联方式?
单向@JoinColumn
关联性能不佳,所以应避免使用它。
最好使用效率更高的双向@JoinColumn
关联性。
JPA JoinColumn vs mappedBy的问题出现的原因是在使用双向关联时,存在使用JoinColumn注解在拥有外键的一方和使用mappedBy注解在另一方的情况。问题在于这种情况下会出现物理信息重复(列名)以及无法优化的SQL查询,这会产生额外的UPDATE语句。
根据文档的说明,由于在JPA规范中,多对一关系几乎总是关系的拥有方,所以一对多关联使用注解(mappedBy=...)来进行标注。
public class Troop { @OneToMany(mappedBy="troop") public SetgetSoldiers() { ... } public class Soldier { @ManyToOne @JoinColumn(name="troop_fk") public Troop getTroop() { ... }
Troop通过属性troop与Soldier建立了双向一对多关系。在mappedBy的一方不需要定义任何物理映射。
为了映射一个双向一对多关系,使一对多关系作为拥有方,需要移除mappedBy元素,并将多对一关系的JoinColumn设置为insertable和updatable为false。这种解决方案并不是最优化的,并且会产生额外的UPDATE语句。
public class Troop { @OneToMany @JoinColumn(name="troop_fk") //需要重复物理信息 public SetgetSoldiers() { ... } public class Soldier { @ManyToOne(insertable=false, updatable=false) @JoinColumn(name="troop_fk") public Troop getTroop() { ... }
我无法确定在您的第二个代码片段中如何将Troop作为拥有方,因为Soldier仍然是拥有外键引用Troop的拥有方(在MySQL中验证过)。
这段代码摘自文档页面(我加粗了)。在您的例子中,注解mappedBy="troop"是指向哪个字段?
注解mappedBy="troop"指的是Soldier类中的属性troop。在上面的代码中,由于Mykhaylo省略了属性的具体实现,所以属性看不到,但是可以通过getter getTroop()推断出属性的存在。可以查看Óscar López的回答,非常清晰,可以理解这一点。
这个例子滥用了JPA 2规范。如果作者的目标是创建双向关联,那么应该在父类上使用mappedBy,在子类上使用JoinColumn(如果需要的话)。使用这种方法,我们得到的是两个独立的单向关联:OneToMany和ManyToOne,但是仅仅是因为运气好(更多是误用),这两个关联使用了相同的外键。
如果你使用的是JPA 2.x,下面的回答可能更清晰一些。不过我建议尝试两种方法,看看Hibernate在生成表时的处理方式。如果是新项目,选择适合你需求的方法。如果是遗留数据库并且不想改变结构,选择与你的模式匹配的方法。
“物理映射”和“虚拟映射”有什么意义?
表定义会有助于澄清外键的位置。
我尝试在父表中使用JoinColumn
,在我的情况下是没有外键的表,当我尝试保存时,它会先创建子类(具有外键的类),并抛出“找不到父键”的错误。
JPA JoinColumn vs mappedBy
在JPA中,我们经常会遇到需要建立实体之间关系的情况。在这种情况下,通常有两个重要的注解可以用来指定关系的拥有者和反向关系:@JoinColumn和mappedBy。
@JoinColumn注解表示该实体是关系的拥有者,即对应表中有一个指向关联表的外键列。而mappedBy属性表示在这一方是关系的反向关系,拥有者实体在“其他”实体中。这也意味着你可以从使用“mappedBy”注解的类中访问另一张表(完全双向关系)。
具体来说,对于问题中的代码,正确的注解应该如下所示:
public class Company { @OneToMany(mappedBy = "company", orphanRemoval = true, fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Listbranches; } public class Branch { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "companyId") private Company company; }
在这两种情况下,Branch都有一个指向Company的字段。
但是,Company表没有一个指向关联表的外键列,而是Branch有一个指向Company的引用。为什么你说“对应表中有一个指向关联表的外键列”?能否解释得更详细一些?
我在我的答案中使用了示例代码进行了更新。注意,在Company中使用@JoinColumn是一个错误。
那么,在OneToMany的情况下,有没有合理的情况可以使用JoinColumn呢?
不,实际上并不完全正确。如果Branch没有一个属性引用Company,但是底层表有一个列引用Company,那么可以使用@JoinColumn来映射它。这是一种不常见的情况,因为通常你会在与表对应的对象中映射列,但是它是合法的。
如果有第二个类,比如“NonProfitCompany”,其中也包含一个List
是的,需要在Branch类中添加另一个类型为NonProfitCompany的私有变量。
这是另一个不喜欢ORM的原因。文档通常太模糊,对我来说,这是纠结于太多魔法领域。我一直在为这个问题苦苦挣扎,但是按照字面意思做时,子行会在其引用父行的FKey列中更新为
null
。
在建立这种关系时,外键是必须的吗?
以这个例子为例,是否可以用外键替换Branch实体中的Company对象?private Long companyId;
然后我应该指定(targetEntity = Company.class)
,而保持不变?
我不确定,建议你进行测试。但是肯定会破坏对象映射,你不应该对列类型进行映射,尽量使用实体对象。
我测试过了,情况很奇怪。如果我加载实体,它可以工作,但是在尝试持久化时却不能。我的策略是避免使用对象,因为我不想强制获取它,而且使用延迟获取给我带来了很多麻烦。
该注解表示该实体是关系的拥有者
这句话对于在子实体中具有外键的单向关系是错误的。例如:vladmihalcea.com/…
简单而强大的回答。